JazzCon 2017: State, Side Effects, and Redux. Oh my!

JazzCon 2017: State, Side Effects, and Redux. Oh my!

94bd558238b69c45d3d3e15797ae94f7?s=128

Jeremy Fairbank

March 23, 2017
Tweet

Transcript

  1. 3.
  2. 5.

    View Model Model Model Model Model View Model View Model

    View Model View Model Model Two-way Data Binding
  3. 7.

    ?

  4. 8.
  5. 26.

    let state = 0; incrementBtn.addEventListener('click', () => { state +=

    1; }); decrementBtn.addEventListener('click', () => { state -= 1; });
  6. 27.

    let state = 0; incrementBtn.addEventListener('click', () => { state +=

    1; }); decrementBtn.addEventListener('click', () => { state -= 1; });
  7. 28.

    let state = 0; incrementBtn.addEventListener('click', () => { state +=

    1; }); decrementBtn.addEventListener('click', () => { state -= 1; });
  8. 29.

    let state = 0; state += 1; state += 1;

    state -= 1; console.log(state); // 1
  9. 30.

    let state = 0; state += 1; state += 1;

    state -= 1; console.log(state); // 1
  10. 31.

    let state = 0; state += 1; state += 1;

    state -= 1; console.log(state); // 1 { type: 'INCREMENT' }; { type: 'INCREMENT' }; { type: 'DECREMENT' };
  11. 32.

    let state = 0; state += 1; state += 1;

    state -= 1; console.log(state); // 1 { type: 'INCREMENT' }; { type: 'INCREMENT' }; { type: 'DECREMENT' }; Action
  12. 33.
  13. 34.

    let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  14. 35.

    let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  15. 36.

    let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  16. 37.

    let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  17. 38.

    let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1 Action
  18. 39.

    let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  19. 40.

    let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  20. 41.

    let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  21. 42.

    let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  22. 43.

    function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  23. 44.

    function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  24. 45.

    function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  25. 46.

    function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  26. 47.

    function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  27. 48.

    function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  28. 49.

    function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  29. 50.
  30. 59.

    store.subscribe(() => { console.log('state =', store.getState()); }); store.dispatch({ type: 'INCREMENT'

    }); store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'DECREMENT' }); // state = 1 // state = 2 // state = 1
  31. 60.

    store.subscribe(() => { console.log('state =', store.getState()); }); store.dispatch({ type: 'INCREMENT'

    }); store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'DECREMENT' }); // state = 1 // state = 2 // state = 1
  32. 61.

    store.subscribe(() => { console.log('state =', store.getState()); }); store.dispatch({ type: 'INCREMENT'

    }); store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'DECREMENT' }); // state = 1 // state = 2 // state = 1
  33. 62.

    store.subscribe(() => { console.log('state =', store.getState()); }); store.dispatch({ type: 'INCREMENT'

    }); store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'DECREMENT' }); // state = 1 // state = 2 // state = 1
  34. 64.

    const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', });
  35. 65.

    const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', });
  36. 66.

    const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', });
  37. 67.

    const unsubscribe = store.subscribe(() => { console.log('state =', store.getState()); });

    store.dispatch(increment()); store.dispatch(increment()); store.dispatch(decrement()); // state = 1 // state = 2 // state = 1
  38. 68.

    const unsubscribe = store.subscribe(() => { console.log('state =', store.getState()); });

    store.dispatch(increment()); store.dispatch(increment()); store.dispatch(decrement()); // state = 1 // state = 2 // state = 1
  39. 69.

    import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  40. 70.

    import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  41. 71.

    import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  42. 72.

    import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  43. 73.

    import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  44. 83.

    function reducer(state = initialState, action) { switch (action.type) { case

    'INCREMENT': return { ...state, counter: state.counter + 1 }; case 'DECREMENT': return { ...state, counter: state.counter - 1 }; case 'CHANGE_COLOR': return { ...state, car: { color: action.payload } }; default: return state; } }
  45. 84.

    function reducer(state = initialState, action) { switch (action.type) { case

    'INCREMENT': return { ...state, counter: state.counter + 1 }; case 'DECREMENT': return { ...state, counter: state.counter - 1 }; case 'CHANGE_COLOR': return { ...state, car: { color: action.payload } }; default: return state; } }
  46. 85.

    function reducer(state = initialState, action) { switch (action.type) { case

    'INCREMENT': return { ...state, counter: state.counter + 1 }; case 'DECREMENT': return { ...state, counter: state.counter - 1 }; case 'CHANGE_COLOR': return { ...state, car: { color: action.payload } }; default: return state; } }
  47. 86.

    function reducer(state = initialState, action) { switch (action.type) { case

    'INCREMENT': return { ...state, counter: state.counter + 1 }; case 'DECREMENT': return { ...state, counter: state.counter - 1 }; case 'CHANGE_COLOR': return { ...state, car: { color: action.payload } }; default: return state; } }
  48. 87.

    function reducer(state = initialState, action) { switch (action.type) { case

    'INCREMENT': return { ...state, counter: state.counter + 1 }; case 'DECREMENT': return { ...state, counter: state.counter - 1 }; case 'CHANGE_COLOR': return { ...state, car: { color: action.payload } }; default: return state; } }
  49. 88.

    function reducer(state = initialState, action) { switch (action.type) { case

    'INCREMENT': return { ...state, counter: state.counter + 1 }; case 'DECREMENT': return { ...state, counter: state.counter - 1 }; case 'CHANGE_COLOR': return { ...state, car: { color: action.payload } }; default: return state; } }
  50. 89.

    const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', }); const changeColor = color => ({ type: 'CHANGE_COLOR', payload: color, });
  51. 90.

    const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', }); const changeColor = color => ({ type: 'CHANGE_COLOR', payload: color, });
  52. 91.

    const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', }); const changeColor = color => ({ type: 'CHANGE_COLOR', payload: color, });
  53. 92.

    store.subscribe(() => { console.log('state =', store.getState()); }); store.dispatch(increment()); store.dispatch(changeColor('green')); //

    state = { counter: 1, car: { color: 'red' } } // state = { counter: 1, car: { color: 'green' } }
  54. 93.

    store.subscribe(() => { console.log('state =', store.getState()); }); store.dispatch(increment()); store.dispatch(changeColor('green')); //

    state = { counter: 1, car: { color: 'red' } } // state = { counter: 1, car: { color: 'green' } }
  55. 94.

    store.subscribe(() => { console.log('state =', store.getState()); }); store.dispatch(increment()); store.dispatch(changeColor('green')); //

    state = { counter: 1, car: { color: 'red' } } // state = { counter: 1, car: { color: 'green' } }
  56. 98.

    const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  57. 99.

    const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  58. 100.

    const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  59. 101.

    const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  60. 102.

    const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  61. 103.

    const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  62. 104.

    const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  63. 105.

    const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  64. 106.

    const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  65. 107.

    import { applyMiddleware } from 'redux'; const store = createStore(

    reducer, applyMiddleware(logMiddleware) ); store.dispatch(increment()); store.dispatch(changeColor('green')); // dispatch { type: 'INCREMENT' } // state = { counter: 1, car: { color: 'red' } } // dispatch { type: 'CHANGE_COLOR', payload: 'green' } // state = { counter: 1, car: { color: 'green' } }
  66. 108.

    import { applyMiddleware } from 'redux'; const store = createStore(

    reducer, applyMiddleware(logMiddleware) ); store.dispatch(increment()); store.dispatch(changeColor('green')); // dispatch { type: 'INCREMENT' } // state = { counter: 1, car: { color: 'red' } } // dispatch { type: 'CHANGE_COLOR', payload: 'green' } // state = { counter: 1, car: { color: 'green' } }
  67. 109.

    import { applyMiddleware } from 'redux'; const store = createStore(

    reducer, applyMiddleware(logMiddleware) ); store.dispatch(increment()); store.dispatch(changeColor('green')); // dispatch { type: 'INCREMENT' } // state = { counter: 1, car: { color: 'red' } } // dispatch { type: 'CHANGE_COLOR', payload: 'green' } // state = { counter: 1, car: { color: 'green' } }
  68. 110.

    import { applyMiddleware } from 'redux'; const store = createStore(

    reducer, applyMiddleware(logMiddleware) ); store.dispatch(increment()); store.dispatch(changeColor('green')); // dispatch { type: 'INCREMENT' } // state = { counter: 1, car: { color: 'red' } } // dispatch { type: 'CHANGE_COLOR', payload: 'green' } // state = { counter: 1, car: { color: 'green' } }
  69. 111.

    import { applyMiddleware } from 'redux'; const store = createStore(

    reducer, applyMiddleware(logMiddleware) ); store.dispatch(increment()); store.dispatch(changeColor('green')); // dispatch { type: 'INCREMENT' } // state = { counter: 1, car: { color: 'red' } } // dispatch { type: 'CHANGE_COLOR', payload: 'green' } // state = { counter: 1, car: { color: 'green' } }
  70. 114.

    function reducer(state = initialState, action) { switch (action.type) { case

    'REQUEST_USERS': return { ...state, isFetching: true }; case 'RECEIVE_USERS': return { ...state, isFetching: false, users: action.payload, }; default: return state; } }
  71. 115.

    function reducer(state = initialState, action) { switch (action.type) { case

    'REQUEST_USERS': return { ...state, isFetching: true }; case 'RECEIVE_USERS': return { ...state, isFetching: false, users: action.payload, }; default: return state; } }
  72. 116.

    function reducer(state = initialState, action) { switch (action.type) { case

    'REQUEST_USERS': return { ...state, isFetching: true }; case 'RECEIVE_USERS': return { ...state, isFetching: false, users: action.payload, }; default: return state; } }
  73. 117.

    const requestUsers = () => ({ type: 'REQUEST_USERS', }); const

    receiveUsers = users => ({ type: 'RECEIVE_USERS', payload: users, });
  74. 118.

    const thunkMiddleware = api => next => action => {

    if (typeof action === 'function') { return action(api.dispatch); } return next(action); }; const store = createStore( reducer, applyMiddleware(thunkMiddleware) );
  75. 119.

    const thunkMiddleware = api => next => action => {

    if (typeof action === 'function') { return action(api.dispatch); } return next(action); }; const store = createStore( reducer, applyMiddleware(thunkMiddleware) );
  76. 120.

    const thunkMiddleware = api => next => action => {

    if (typeof action === 'function') { return action(api.dispatch); } return next(action); }; const store = createStore( reducer, applyMiddleware(thunkMiddleware) );
  77. 121.

    const thunkMiddleware = api => next => action => {

    if (typeof action === 'function') { return action(api.dispatch); } return next(action); }; const store = createStore( reducer, applyMiddleware(thunkMiddleware) );
  78. 122.

    function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }); }; } store.dispatch(fetchUsers());
  79. 123.

    function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }); }; } store.dispatch(fetchUsers()); Action Creator
  80. 124.

    function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }); }; } store.dispatch(fetchUsers()); Action
  81. 125.

    function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }); }; } store.dispatch(fetchUsers());
  82. 126.
  83. 136.
  84. 137.

    Redux Saga Sagas IO APIs Console DB call, apply call,

    apply, fork, spawn, join, cancel Redux Store put, select, take
  85. 143.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  86. 144.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  87. 145.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  88. 146.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  89. 147.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  90. 148.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(); // { value: 'world', done: false }
  91. 149.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(); // { value: 'world', done: false }
  92. 150.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(21); // { value: 42, done: true }
  93. 151.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(21); // { value: 42, done: true }
  94. 152.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(21); // { value: 42, done: true }
  95. 153.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(); // { value: undefined, done: true }
  96. 154.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(); // { value: undefined, done: true }
  97. 155.

    function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(); // { value: undefined, done: true }
  98. 156.

    import { call, put, take } from 'redux-saga/effects'; function* fetchUsersSaga()

    { yield take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } store.dispatch(requestUsers());
  99. 157.

    import { call, put, take } from 'redux-saga/effects'; function* fetchUsersSaga()

    { yield take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } store.dispatch(requestUsers());
  100. 158.

    import { call, put, take } from 'redux-saga/effects'; function* fetchUsersSaga()

    { yield take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } store.dispatch(requestUsers());
  101. 159.

    import { call, put, take } from 'redux-saga/effects'; function* fetchUsersSaga()

    { yield take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } store.dispatch(requestUsers());
  102. 160.

    import { call, put, take } from 'redux-saga/effects'; function* fetchUsersSaga()

    { yield take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } store.dispatch(requestUsers());
  103. 161.

    import { call, put, take } from 'redux-saga/effects'; function* fetchUsersSaga()

    { yield take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } store.dispatch(requestUsers());
  104. 162.

    import { call, put, take } from 'redux-saga/effects'; function* fetchUsersSaga()

    { yield take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } store.dispatch(requestUsers());
  105. 163.

    import { call, put, take } from 'redux-saga/effects'; function* fetchUsersSaga()

    { yield take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } store.dispatch(requestUsers());
  106. 164.

    import { call, put, take } from 'redux-saga/effects'; function* fetchUsersSaga()

    { yield take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } store.dispatch(requestUsers());
  107. 170.
  108. 171.

    import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  109. 172.

    import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  110. 173.

    import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  111. 174.

    import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  112. 175.

    import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  113. 176.
  114. 180.
  115. 181.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } const saga = fetchUsersSaga(); it('waits for REQUEST_USERS', () => { const actual = saga.next().value; const expected = take('REQUEST_USERS'); expect(actual).toEqual(expected); });
  116. 182.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } const saga = fetchUsersSaga(); it('waits for REQUEST_USERS', () => { const actual = saga.next().value; const expected = take('REQUEST_USERS'); expect(actual).toEqual(expected); });
  117. 183.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } const saga = fetchUsersSaga(); it('waits for REQUEST_USERS', () => { const actual = saga.next().value; const expected = take('REQUEST_USERS'); expect(actual).toEqual(expected); });
  118. 184.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } const saga = fetchUsersSaga(); it('waits for REQUEST_USERS', () => { const actual = saga.next().value; const expected = take('REQUEST_USERS'); expect(actual).toEqual(expected); });
  119. 185.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } const saga = fetchUsersSaga(); it('waits for REQUEST_USERS', () => { const actual = saga.next().value; const expected = take('REQUEST_USERS'); expect(actual).toEqual(expected); });
  120. 186.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } // ... it('fetches users', () => { const actual = saga.next().value; const expected = call(axios.get, '/users'); expect(actual).toEqual(expected); });
  121. 187.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } // ... it('fetches users', () => { const actual = saga.next().value; const expected = call(axios.get, '/users'); expect(actual).toEqual(expected); });
  122. 188.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } // ... it('fetches users', () => { const actual = saga.next().value; const expected = call(axios.get, '/users'); expect(actual).toEqual(expected); });
  123. 189.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } // ... it('fetches users', () => { const actual = saga.next().value; const expected = call(axios.get, '/users'); expect(actual).toEqual(expected); });
  124. 190.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } it('dispatches the users', () => { const users = [{ name: 'Bob' }, { name: 'Alice' }]; const response = { data: users }; const actual = saga.next(response).value; const expected = put(receiveUsers(users)); expect(actual).toEqual(expected); });
  125. 191.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } it('dispatches the users', () => { const users = [{ name: 'Bob' }, { name: 'Alice' }]; const response = { data: users }; const actual = saga.next(response).value; const expected = put(receiveUsers(users)); expect(actual).toEqual(expected); });
  126. 192.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } it('dispatches the users', () => { const users = [{ name: 'Bob' }, { name: 'Alice' }]; const response = { data: users }; const actual = saga.next(response).value; const expected = put(receiveUsers(users)); expect(actual).toEqual(expected); });
  127. 193.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } it('dispatches the users', () => { const users = [{ name: 'Bob' }, { name: 'Alice' }]; const response = { data: users }; const actual = saga.next(response).value; const expected = put(receiveUsers(users)); expect(actual).toEqual(expected); });
  128. 194.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } it('dispatches the users', () => { const users = [{ name: 'Bob' }, { name: 'Alice' }]; const response = { data: users }; const actual = saga.next(response).value; const expected = put(receiveUsers(users)); expect(actual).toEqual(expected); });
  129. 195.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } it('dispatches the users', () => { const users = [{ name: 'Bob' }, { name: 'Alice' }]; const response = { data: users }; const actual = saga.next(response).value; const expected = put(receiveUsers(users)); expect(actual).toEqual(expected); });
  130. 200.

    function reducer(state = initialState, action) { switch (action.type) { //

    ... case 'FAIL_USERS': return { ...state, error: action.error }; // ... } }
  131. 201.

    function reducer(state = initialState, action) { switch (action.type) { //

    ... case 'FAIL_USERS': return { ...state, error: action.error }; // ... } }
  132. 202.

    function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }) .catch((e) => { dispatch(failUsers(e)); }); }; }
  133. 203.

    function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }) .catch((e) => { dispatch(failUsers(e)); }); }; } Swallowed without catch
  134. 205.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } No swallowed errors
  135. 206.

    function* fetchUsersSaga() { try { yield take('REQUEST_USERS'); const response =

    yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } catch (e) { yield put(failUsers(e)); } }
  136. 207.

    function* fetchUsersSaga() { try { yield take('REQUEST_USERS'); const response =

    yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } catch (e) { yield put(failUsers(e)); } }
  137. 208.

    function* fetchUsersSaga() { try { yield take('REQUEST_USERS'); const response =

    yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } catch (e) { yield put(failUsers(e)); } }
  138. 209.

    function* fetchUsersSaga() { try { yield take('REQUEST_USERS'); const response =

    yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } catch (e) { yield put(failUsers(e)); } }
  139. 211.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const responses = yield [

    call(axios.get, '/users/1'), call(axios.get, '/users/2'), call(axios.get, '/users/3'), ]; const users = responses.map(resp => resp.data); yield put(receiveUsers(users)); }
  140. 212.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const responses = yield [

    call(axios.get, '/users/1'), call(axios.get, '/users/2'), call(axios.get, '/users/3'), ]; const users = responses.map(resp => resp.data); yield put(receiveUsers(users)); }
  141. 213.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const responses = yield [

    call(axios.get, '/users/1'), call(axios.get, '/users/2'), call(axios.get, '/users/3'), ]; const users = responses.map(resp => resp.data); yield put(receiveUsers(users)); }
  142. 214.

    function* fetchUsersSaga() { yield take('REQUEST_USERS'); const responses = yield [

    call(axios.get, '/users/1'), call(axios.get, '/users/2'), call(axios.get, '/users/3'), ]; const users = responses.map(resp => resp.data); yield put(receiveUsers(users)); }
  143. 221.

    function* fetchUsersSaga() { const response = yield call(axios.get, '/users'); yield

    put(receiveUsers(response.data)); } function* mainSaga() { while (true) { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); } }
  144. 222.

    function* fetchUsersSaga() { const response = yield call(axios.get, '/users'); yield

    put(receiveUsers(response.data)); } function* mainSaga() { while (true) { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); } }
  145. 223.

    import { takeEvery } from 'redux-saga/effects'; function* fetchUsersSaga() { const

    response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield takeEvery('REQUEST_USERS', fetchUsersSaga); }
  146. 224.

    import { takeEvery } from 'redux-saga/effects'; function* fetchUsersSaga() { const

    response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield takeEvery('REQUEST_USERS', fetchUsersSaga); }
  147. 225.

    import { takeEvery } from 'redux-saga/effects'; function* fetchUsersSaga() { const

    response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield takeEvery('REQUEST_USERS', fetchUsersSaga); }
  148. 226.

    import { takeEvery } from 'redux-saga/effects'; function* fetchUsersSaga() { const

    response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield takeEvery('REQUEST_USERS', fetchUsersSaga); }
  149. 227.

    import { takeEvery } from 'redux-saga/effects'; function* fetchUsersSaga() { const

    response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield takeEvery('REQUEST_USERS', fetchUsersSaga); }
  150. 236.
  151. 237.

    function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  152. 238.

    function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  153. 239.

    function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  154. 240.

    function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  155. 241.

    function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  156. 242.

    function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout()); Missed
  157. 243.

    function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout()); Missed Never reached
  158. 249.

    import { fork } from 'redux-saga/effects'; function* fetchUsersSaga() { yield

    take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield fork(fetchUsersSaga); yield take('LOGOUT'); yield call(logoutSaga); }
  159. 250.

    import { fork } from 'redux-saga/effects'; function* fetchUsersSaga() { yield

    take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield fork(fetchUsersSaga); yield take('LOGOUT'); yield call(logoutSaga); }
  160. 251.

    import { fork } from 'redux-saga/effects'; function* fetchUsersSaga() { yield

    take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield fork(fetchUsersSaga); yield take('LOGOUT'); yield call(logoutSaga); } Nonblocking
  161. 252.

    import { fork } from 'redux-saga/effects'; function* fetchUsersSaga() { yield

    take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield fork(fetchUsersSaga); yield take('LOGOUT'); yield call(logoutSaga); }
  162. 253.

    import { fork } from 'redux-saga/effects'; function* fetchUsersSaga() { yield

    take('REQUEST_USERS'); const response = yield call(axios.get, '/users'); yield put(receiveUsers(response.data)); } function* mainSaga() { yield fork(fetchUsersSaga); yield take('LOGOUT'); yield call(logoutSaga); } Won’t miss now
  163. 254.
  164. 264.

    Miscellaneous Patterns • Autosaving background tasks • Races for timeouts

    • Task cancellation • Throttling and debouncing
  165. 265.

    Miscellaneous Patterns • Autosaving background tasks • Races for timeouts

    • Task cancellation • Throttling and debouncing • Hook up to other IO sources
  166. 266.

    Resources •Redux • redux.js.org • egghead.io/courses/getting-started-with-redux •React • github.com/reactjs/react-redux •Redux

    Saga • redux-saga.github.io/redux-saga • Testing • github.com/jfairbank/redux-saga-test-plan