Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

Jeremy Fairbank

March 23, 2017
Tweet

More Decks by Jeremy Fairbank

Other Decks in Programming

Transcript

  1. View Model Model Model Model Model View Model View Model

    View Model View Model Model Two-way Data Binding
  2. ?

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

    1; }); decrementBtn.addEventListener('click', () => { state -= 1; });
  4. let state = 0; incrementBtn.addEventListener('click', () => { state +=

    1; }); decrementBtn.addEventListener('click', () => { state -= 1; });
  5. let state = 0; incrementBtn.addEventListener('click', () => { state +=

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

    state -= 1; console.log(state); // 1
  7. let state = 0; state += 1; state += 1;

    state -= 1; console.log(state); // 1
  8. let state = 0; state += 1; state += 1;

    state -= 1; console.log(state); // 1 { type: 'INCREMENT' }; { type: 'INCREMENT' }; { type: 'DECREMENT' };
  9. let state = 0; state += 1; state += 1;

    state -= 1; console.log(state); // 1 { type: 'INCREMENT' }; { type: 'INCREMENT' }; { type: 'DECREMENT' }; Action
  10. let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  11. let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  12. let state = 0; state = reducer(state, { type: 'INCREMENT'

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  13. let state = 0; state = reducer(state, { type: 'INCREMENT'

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

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

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

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

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

    }); state = reducer(state, { type: 'INCREMENT' }); state = reducer(state, { type: 'DECREMENT' }); console.log(state); // 1
  19. function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  20. function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  21. function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  22. function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  23. function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  24. function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  25. function reducer(state = 0, action) { switch (action.type) { case

    'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } }
  26. 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
  27. 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
  28. 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
  29. 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
  30. const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', });
  31. const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', });
  32. const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', });
  33. const unsubscribe = store.subscribe(() => { console.log('state =', store.getState()); });

    store.dispatch(increment()); store.dispatch(increment()); store.dispatch(decrement()); // state = 1 // state = 2 // state = 1
  34. const unsubscribe = store.subscribe(() => { console.log('state =', store.getState()); });

    store.dispatch(increment()); store.dispatch(increment()); store.dispatch(decrement()); // state = 1 // state = 2 // state = 1
  35. import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  36. import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  37. import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  38. import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  39. import { bindActionCreators } from 'redux'; const actions = bindActionCreators(

    { increment, decrement }, store.dispatch ); actions.increment(); actions.increment(); actions.decrement(); // state = 1 // state = 2 // state = 1
  40. 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; } }
  41. 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; } }
  42. 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; } }
  43. 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; } }
  44. 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. 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. const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', }); const changeColor = color => ({ type: 'CHANGE_COLOR', payload: color, });
  47. const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', }); const changeColor = color => ({ type: 'CHANGE_COLOR', payload: color, });
  48. const increment = () => ({ type: 'INCREMENT', }); const

    decrement = () => ({ type: 'DECREMENT', }); const changeColor = color => ({ type: 'CHANGE_COLOR', payload: color, });
  49. 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' } }
  50. 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' } }
  51. 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' } }
  52. const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  53. const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  54. const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  55. const logMiddleware = api => next => action => {

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  56. const logMiddleware = api => next => action => {

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

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

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

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

    console.log('dispatch', action); const result = next(action); console.log('state =', api.getState()); return result; };
  61. 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' } }
  62. 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' } }
  63. 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' } }
  64. 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' } }
  65. 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. 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; } }
  67. 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; } }
  68. 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; } }
  69. const requestUsers = () => ({ type: 'REQUEST_USERS', }); const

    receiveUsers = users => ({ type: 'RECEIVE_USERS', payload: users, });
  70. const thunkMiddleware = api => next => action => {

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

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

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

    if (typeof action === 'function') { return action(api.dispatch); } return next(action); }; const store = createStore( reducer, applyMiddleware(thunkMiddleware) );
  74. function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }); }; } store.dispatch(fetchUsers());
  75. function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }); }; } store.dispatch(fetchUsers()); Action Creator
  76. function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }); }; } store.dispatch(fetchUsers()); Action
  77. function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }); }; } store.dispatch(fetchUsers());
  78. Redux Saga Sagas IO APIs Console DB call, apply call,

    apply, fork, spawn, join, cancel Redux Store put, select, take
  79. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  80. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  81. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  82. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  83. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } const iterator = myGenerator(); iterator.next(); // { value: 'hello', done: false }
  84. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(); // { value: 'world', done: false }
  85. function* myGenerator() { yield 'hello'; const value = yield 'world';

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

    return value * 2; } iterator.next(21); // { value: 42, done: true }
  87. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(21); // { value: 42, done: true }
  88. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(21); // { value: 42, done: true }
  89. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(); // { value: undefined, done: true }
  90. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(); // { value: undefined, done: true }
  91. function* myGenerator() { yield 'hello'; const value = yield 'world';

    return value * 2; } iterator.next(); // { value: undefined, done: true }
  92. 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());
  93. 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());
  94. 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());
  95. 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());
  96. 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());
  97. 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());
  98. 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. 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. 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. import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  102. import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  103. import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  104. import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  105. import createSagaMiddleware from 'redux-saga'; const sagaMiddleware = createSagaMiddleware(); const store

    = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(fetchUsersSaga);
  106. 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); });
  107. 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); });
  108. 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); });
  109. 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); });
  110. 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); });
  111. 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); });
  112. 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); });
  113. 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); });
  114. 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); });
  115. 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); });
  116. 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); });
  117. 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); });
  118. 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); });
  119. 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); });
  120. 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); });
  121. function reducer(state = initialState, action) { switch (action.type) { //

    ... case 'FAIL_USERS': return { ...state, error: action.error }; // ... } }
  122. function reducer(state = initialState, action) { switch (action.type) { //

    ... case 'FAIL_USERS': return { ...state, error: action.error }; // ... } }
  123. function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }) .catch((e) => { dispatch(failUsers(e)); }); }; }
  124. function fetchUsers() { return dispatch => { dispatch(requestUsers()); return axios.get('/users')

    .then(({ data }) => { dispatch(receiveUsers(data)); }) .catch((e) => { dispatch(failUsers(e)); }); }; } Swallowed without catch
  125. function* fetchUsersSaga() { yield take('REQUEST_USERS'); const response = yield call(axios.get,

    '/users'); yield put(receiveUsers(response.data)); } No swallowed errors
  126. 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)); } }
  127. 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)); } }
  128. 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)); } }
  129. 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)); } }
  130. 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)); }
  131. 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)); }
  132. 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)); }
  133. 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)); }
  134. 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); } }
  135. 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); } }
  136. 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); }
  137. 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); }
  138. 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); }
  139. 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); }
  140. 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); }
  141. function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  142. function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  143. function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  144. function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  145. function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout());
  146. function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout()); Missed
  147. function* mainSaga() { yield take('REQUEST_USERS'); yield call(fetchUsersSaga); yield take('LOGOUT'); yield

    call(logoutSaga); } store.dispatch(requestUsers()); store.dispatch(logout()); Missed Never reached
  148. 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); }
  149. 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); }
  150. 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
  151. 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); }
  152. 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
  153. Miscellaneous Patterns • Autosaving background tasks • Races for timeouts

    • Task cancellation • Throttling and debouncing
  154. Miscellaneous Patterns • Autosaving background tasks • Races for timeouts

    • Task cancellation • Throttling and debouncing • Hook up to other IO sources
  155. 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