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. State,
    Side Effects,
    and Redux
    Oh my!
    Jeremy Fairbank
    @elpapapollo / jfairbank

    View full-size slide

  2. Controller
    View
    Model
    Model
    Model
    Model
    Model
    View
    View
    View
    View
    MVC

    View full-size slide

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

    View full-size slide

  4. Disorganized Changes
    {...}

    View full-size slide

  5. Redux
    Unidirectional
    Organized changes
    Read-only state

    View full-size slide

  6. Reducer
    View
    State
    Actions

    View full-size slide

  7. Reducer
    View
    State
    Actions

    View full-size slide

  8. Reducer
    View
    State
    Actions

    View full-size slide

  9. Reducer
    View
    React,
    Angular,
    Vue,
    Vanilla JS,
    etc.
    State
    Actions

    View full-size slide

  10. Reducer
    View
    State
    Actions
    Dispatch

    View full-size slide

  11. Reducer
    View
    State
    Actions
    Repeat

    View full-size slide

  12. Store
    Reducer
    State
    View

    View full-size slide

  13. Store
    Reducer
    State
    View

    View full-size slide

  14. Store
    Reducer
    State
    View

    View full-size slide

  15. Store
    Reducer
    State
    View

    View full-size slide

  16. Store
    Reducer
    State
    View
    Action

    View full-size slide

  17. Store
    Reducer
    State
    View
    Action

    View full-size slide

  18. Store
    Reducer
    State
    View

    View full-size slide

  19. Store
    Reducer
    State
    View

    View full-size slide

  20. Store
    Reducer
    State
    View

    View full-size slide

  21. Store
    Reducer
    State
    View

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  45. import { createStore } from 'redux';
    const store = createStore(reducer);
    store.getState(); // 0

    View full-size slide

  46. import { createStore } from 'redux';
    const store = createStore(reducer);
    store.getState(); // 0

    View full-size slide

  47. import { createStore } from 'redux';
    const store = createStore(reducer);
    store.getState(); // 0

    View full-size slide

  48. import { createStore } from 'redux';
    const store = createStore(reducer);
    store.getState(); // 0

    View full-size slide

  49. store.dispatch({ type: 'INCREMENT' });
    store.dispatch({ type: 'INCREMENT' });
    store.dispatch({ type: 'DECREMENT' });
    store.getState(); // 1

    View full-size slide

  50. store.dispatch({ type: 'INCREMENT' });
    store.dispatch({ type: 'INCREMENT' });
    store.dispatch({ type: 'DECREMENT' });
    store.getState(); // 1

    View full-size slide

  51. store.dispatch({ type: 'INCREMENT' });
    store.dispatch({ type: 'INCREMENT' });
    store.dispatch({ type: 'DECREMENT' });
    store.getState(); // 1

    View full-size slide

  52. store.dispatch({ type: 'INCREMENT' });
    store.dispatch({ type: 'INCREMENT' });
    store.dispatch({ type: 'DECREMENT' });
    store.getState(); // 1

    View full-size slide

  53. 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

    View full-size slide

  54. 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

    View full-size slide

  55. 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

    View full-size slide

  56. 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

    View full-size slide

  57. Action
    Creators

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  68. incrementBtn.addEventListener('click', actions.increment);
    decrementBtn.addEventListener('click', actions.decrement);
    store.subscribe(() => {
    counterElement.innerHTML = store.getState();
    });
    incrementBtn.click();
    incrementBtn.click();
    decrementBtn.click();
    console.log(counterElement.innerHTML); // 1

    View full-size slide

  69. incrementBtn.addEventListener('click', actions.increment);
    decrementBtn.addEventListener('click', actions.decrement);
    store.subscribe(() => {
    counterElement.innerHTML = store.getState();
    });
    incrementBtn.click();
    incrementBtn.click();
    decrementBtn.click();
    console.log(counterElement.innerHTML); // 1

    View full-size slide

  70. incrementBtn.addEventListener('click', actions.increment);
    decrementBtn.addEventListener('click', actions.decrement);
    store.subscribe(() => {
    counterElement.innerHTML = store.getState();
    });
    incrementBtn.click();
    incrementBtn.click();
    decrementBtn.click();
    console.log(counterElement.innerHTML); // 1

    View full-size slide

  71. incrementBtn.addEventListener('click', actions.increment);
    decrementBtn.addEventListener('click', actions.decrement);
    store.subscribe(() => {
    counterElement.innerHTML = store.getState();
    });
    incrementBtn.click();
    incrementBtn.click();
    decrementBtn.click();
    console.log(counterElement.innerHTML); // 1

    View full-size slide

  72. incrementBtn.addEventListener('click', actions.increment);
    decrementBtn.addEventListener('click', actions.decrement);
    store.subscribe(() => {
    counterElement.innerHTML = store.getState();
    });
    incrementBtn.click();
    incrementBtn.click();
    decrementBtn.click();
    console.log(counterElement.innerHTML); // 1

    View full-size slide

  73. Immutable
    Object State

    View full-size slide

  74. const initialState = {
    counter: 0,
    car: {
    color: 'red',
    },
    };

    View full-size slide

  75. const initialState = {
    counter: 0,
    car: {
    color: 'red',
    },
    };

    View full-size slide

  76. const initialState = {
    counter: 0,
    car: {
    color: 'red',
    },
    };

    View full-size slide

  77. 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;
    }
    }

    View full-size slide

  78. 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;
    }
    }

    View full-size slide

  79. 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;
    }
    }

    View full-size slide

  80. 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;
    }
    }

    View full-size slide

  81. 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;
    }
    }

    View full-size slide

  82. 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;
    }
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  86. 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' } }

    View full-size slide

  87. 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' } }

    View full-size slide

  88. 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' } }

    View full-size slide

  89. Reducer
    View
    State
    Actions
    Middleware

    View full-size slide

  90. Reducer
    View
    State
    Actions
    Middleware
    Intercept

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  100. 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' } }

    View full-size slide

  101. 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' } }

    View full-size slide

  102. 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' } }

    View full-size slide

  103. 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' } }

    View full-size slide

  104. 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' } }

    View full-size slide

  105. Side Effects
    I/O
    Mutable
    State

    View full-size slide

  106. const initialState = {
    users: [],
    isFetching: false,
    };

    View full-size slide

  107. 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;
    }
    }

    View full-size slide

  108. 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;
    }
    }

    View full-size slide

  109. 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;
    }
    }

    View full-size slide

  110. const requestUsers = () => ({
    type: 'REQUEST_USERS',
    });
    const receiveUsers = users => ({
    type: 'RECEIVE_USERS',
    payload: users,
    });

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  119. Reducer
    View
    State
    Actions
    Middleware
    Redux
    Saga

    View full-size slide

  120. Reducer
    View
    State
    Actions
    Middleware
    Redux
    Saga

    View full-size slide

  121. Reducer
    View
    State
    Actions
    Middleware
    Redux
    Saga

    View full-size slide

  122. Reducer
    View
    State
    Actions
    Middleware
    Redux
    Saga

    View full-size slide

  123. Effect Descriptors
    take('SOME_ACTION_TYPE')
    call(someAsyncFunction)
    put(someAction())
    fork(someSagaFunction)
    Just values

    View full-size slide

  124. Redux Saga
    Sagas

    View full-size slide

  125. Redux Saga
    Sagas

    View full-size slide

  126. Redux Saga
    Sagas

    View full-size slide

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

    View full-size slide

  128. Redux Saga
    Sagas
    IO
    APIs
    Console
    DB
    call, apply
    call, apply,
    fork, spawn,
    join, cancel

    View full-size slide

  129. Redux Saga
    Sagas
    IO
    APIs
    Console
    DB
    call, apply
    call, apply,
    fork, spawn,
    join, cancel
    Redux
    Store
    put, select, take

    View full-size slide

  130. Generator
    Functions

    View full-size slide

  131. function* myGenerator() {
    yield 'hello';
    const value = yield 'world';
    return value * 2;
    }

    View full-size slide

  132. function* myGenerator() {
    yield 'hello';
    const value = yield 'world';
    return value * 2;
    }

    View full-size slide

  133. function* myGenerator() {
    yield 'hello';
    const value = yield 'world';
    return value * 2;
    }

    View full-size slide

  134. function* myGenerator() {
    yield 'hello';
    const value = yield 'world';
    return value * 2;
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  148. 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());

    View full-size slide

  149. 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());

    View full-size slide

  150. 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());

    View full-size slide

  151. 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());

    View full-size slide

  152. 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());

    View full-size slide

  153. 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());

    View full-size slide

  154. 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());

    View full-size slide

  155. 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());

    View full-size slide

  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());

    View full-size slide

  157. Saga
    Redux Saga
    yield take('REQUEST_USERS');
    1. Wait for action

    View full-size slide

  158. Saga
    Redux Saga
    View
    Middleware
    yield take('REQUEST_USERS');
    2. Receive action

    View full-size slide

  159. Saga
    Redux Saga
    yield call(axios.get, '/users');
    3. Call API

    View full-size slide

  160. const response =
    yield call(axios.get, '/users');
    Saga
    Redux Saga
    4. Receive response

    View full-size slide

  161. Saga
    Redux Saga
    Reducer
    Redux
    Store
    yield put(
    receiveUsers(response.data)
    );
    5. Dispatch (put) action

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  167. Business Logic Spread Out
    Component
    Thunk
    Service
    Service
    Component
    Reducer

    View full-size slide

  168. Business Logic Spread Out
    Component
    Thunk
    Service
    Service
    Component
    ×
    Reducer

    View full-size slide

  169. Business Logic Consolidated
    Saga Saga Saga
    Saga
    Saga
    Saga

    View full-size slide

  170. 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);
    });

    View full-size slide

  171. 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);
    });

    View full-size slide

  172. 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);
    });

    View full-size slide

  173. 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);
    });

    View full-size slide

  174. 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);
    });

    View full-size slide

  175. 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);
    });

    View full-size slide

  176. 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);
    });

    View full-size slide

  177. 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);
    });

    View full-size slide

  178. 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);
    });

    View full-size slide

  179. 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);
    });

    View full-size slide

  180. 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);
    });

    View full-size slide

  181. 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);
    });

    View full-size slide

  182. 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);
    });

    View full-size slide

  183. 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);
    });

    View full-size slide

  184. 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);
    });

    View full-size slide

  185. Error
    Handling

    View full-size slide

  186. const initialState = {
    users: [],
    isFetching: false,
    error: null,
    };

    View full-size slide

  187. const initialState = {
    users: [],
    isFetching: false,
    error: null,
    };

    View full-size slide

  188. const failUsers = error => ({
    type: 'FAIL_USERS',
    error,
    });

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  193. function* fetchUsersSaga() {
    yield take('REQUEST_USERS');
    const response = yield call(axios.get, '/users');
    yield put(receiveUsers(response.data));
    }

    View full-size slide

  194. function* fetchUsersSaga() {
    yield take('REQUEST_USERS');
    const response = yield call(axios.get, '/users');
    yield put(receiveUsers(response.data));
    }
    No swallowed errors

    View full-size slide

  195. 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));
    }
    }

    View full-size slide

  196. 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));
    }
    }

    View full-size slide

  197. 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));
    }
    }

    View full-size slide

  198. 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));
    }
    }

    View full-size slide

  199. Multiple
    Requests

    View full-size slide

  200. 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));
    }

    View full-size slide

  201. 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));
    }

    View full-size slide

  202. 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));
    }

    View full-size slide

  203. 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));
    }

    View full-size slide

  204. Saga
    yield [
    call(axios.get, '/users/1'),
    call(axios.get, '/users/2'),
    call(axios.get, '/users/3'),
    ];
    // ...

    View full-size slide

  205. Saga
    yield [
    call(axios.get, '/users/1'),
    call(axios.get, '/users/2'),
    call(axios.get, '/users/3'),
    ];
    // ...
    1
    2
    3

    View full-size slide

  206. Saga
    yield [
    call(axios.get, '/users/1'),
    call(axios.get, '/users/2'),
    call(axios.get, '/users/3'),
    ];
    // ...
    1
    2
    3

    View full-size slide

  207. Saga
    yield [
    call(axios.get, '/users/1'),
    call(axios.get, '/users/2'),
    call(axios.get, '/users/3'),
    ];
    // ...
    1
    3

    View full-size slide

  208. Saga
    yield [
    call(axios.get, '/users/1'),
    call(axios.get, '/users/2'),
    call(axios.get, '/users/3'),
    ];
    // ...
    3

    View full-size slide

  209. Saga Helpers

    View full-size slide

  210. 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);
    }
    }

    View full-size slide

  211. 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);
    }
    }

    View full-size slide

  212. 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);
    }

    View full-size slide

  213. 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);
    }

    View full-size slide

  214. 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);
    }

    View full-size slide

  215. 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);
    }

    View full-size slide

  216. 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);
    }

    View full-size slide

  217. mainSaga
    fetchUsersSaga
    yield takeEvery(
    'REQUEST_USERS',
    fetchUsersSaga
    );

    View full-size slide

  218. View
    Middleware
    store.dispatch(
    requestUsers()
    );
    mainSaga
    fetchUsersSaga

    View full-size slide

  219. mainSaga
    fetchUsersSaga
    fork

    View full-size slide

  220. mainSaga
    fetchUsersSaga
    yield call(
    axios.get,
    '/users'
    );

    View full-size slide

  221. mainSaga
    fetchUsersSaga
    const response =
    yield call(
    axios.get,
    '/users'
    );

    View full-size slide

  222. mainSaga
    fetchUsersSaga
    yield put(
    receiveUsers(
    response.data
    )
    );
    Reducer
    Redux
    Store

    View full-size slide

  223. mainSaga
    fetchUsersSaga
    yield takeEvery(
    'REQUEST_USERS',
    fetchUsersSaga
    );
    And repeat…

    View full-size slide

  224. Data Race Issues
    Use alternative takeLatest

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  232. mainSaga
    yield take(
    'REQUEST_USERS'
    );

    View full-size slide

  233. View
    Middleware
    store.dispatch(
    requestUsers()
    );
    mainSaga

    View full-size slide

  234. mainSaga
    fetchUsersSaga
    call

    View full-size slide

  235. mainSaga
    fetchUsersSaga
    call
    yield call(
    fetchUsersSaga
    );

    View full-size slide

  236. mainSaga
    fetchUsersSaga
    call
    View
    Middleware
    store.dispatch(
    logout()
    );
    ×

    View full-size slide

  237. 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);
    }

    View full-size slide

  238. 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);
    }

    View full-size slide

  239. 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

    View full-size slide

  240. 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);
    }

    View full-size slide

  241. 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

    View full-size slide

  242. mainSaga
    fetchUsersSaga
    fork
    yield fork(
    fetchUsersSaga
    );

    View full-size slide

  243. View
    Middleware
    store.dispatch(
    requestUsers()
    );
    mainSaga
    fetchUsersSaga

    View full-size slide

  244. mainSaga
    fetchUsersSaga
    yield call(
    axios.get,
    '/users'
    );

    View full-size slide

  245. mainSaga
    fetchUsersSaga
    yield take(
    'LOGOUT'
    );

    View full-size slide

  246. mainSaga
    fetchUsersSaga
    View
    Middleware
    store.dispatch(
    logout()
    );

    View full-size slide

  247. Miscellaneous Patterns

    View full-size slide

  248. Miscellaneous Patterns
    • Autosaving background tasks

    View full-size slide

  249. Miscellaneous Patterns
    • Autosaving background tasks
    • Races for timeouts

    View full-size slide

  250. Miscellaneous Patterns
    • Autosaving background tasks
    • Races for timeouts
    • Task cancellation

    View full-size slide

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

    View full-size slide

  252. Miscellaneous Patterns
    • Autosaving background tasks
    • Races for timeouts
    • Task cancellation
    • Throttling and debouncing
    • Hook up to other IO sources

    View full-size slide

  253. 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

    View full-size slide

  254. Thanks!
    Code:
    github.com/jfairbank/state-side-effects-and-redux
    Slides:
    bit.ly/jazzcon-redux
    Jeremy Fairbank
    @elpapapollo / jfairbank

    View full-size slide