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

Redux Deep Dive - Dispatch your Reducer - ReactFoo Pune 2018 - Aziz Khambati

Redux Deep Dive - Dispatch your Reducer - ReactFoo Pune 2018 - Aziz Khambati

Redux Deep Dive - Dispatch your Reducer - ReactFoo Pune 2018 - Aziz Khambati

0ef812322b4f0b770af270f081556a42?s=128

Aziz Khambati

January 19, 2018
Tweet

Transcript

  1. Redux Deep Dive

  2. Destructuring an Object 1. const newState = { 2. ...state,

    3. key1: value1, 4. key2: value2 5. } 1. const newState = _.extend( 2. {}, 3. state, 4. { 5. key1, value1, 6. key2, value2 7. } 8. )
  3. Agenda 1. Redux Basics (Brief) 2. Enhancers, Middlewares 3. Problems

    1. Typos, Types, Tree Shaking, Chunking 2. Partial but failing Solutions 4. Solution
  4. React’s Philosophy Props / State VDOM DOM render Pure Function

    React
  5. React Philosophy Events Props / State VDOM DOM render Pure

    Function React setState
  6. As your App grows, it becomes difficult to manage using

    setState
  7. Enter Redux

  8. Redux Store State Component Action Reducers

  9. Provide store at root level 1. import { createStore }

    from 'redux' 2. import { Provider } from 'react-redux'; 3. 4. import reducer from './reducers'; 5. 6. const store = createStore(reducer); 7. 8. export default () => { 9. return ( 10. <Provider store={store}> 11. <App> 12. </Provider> 13. ) 14. };
  10. Store’s State passed to Components 1. import { connect }

    from 'react-redux' 2. 3. class HeaderComponent extends React.Component { 4. render () { 5. return ( 6. <h1> {this.props.user} </h1> 7. ) 8. } 9. } 10. 11. const mapStateToProps = (state) => ({ 12. user: state.user 13. }); 14. 15. export default connect(mapStateToProps)(HeaderComponent);
  11. Dispatch an Action 1. class Counter extends React.Component { 2.

    onClick = (e) => { 3. this.props.dispatch({ 4. type: 'COUNTER_CLICKED', 5. payload: e.currentTarget.data.id 6. }) 7. } 8. render () { 9. return <div 10. onClick={this.onClick(e)} 11. data-id={this.props.id} 12. /> 13. } 14. }
  12. Reducer 1. export default function reducer(state, action) { 2. switch

    (action.type) { 3. case 'COUNTER_CLICKED': 4. return { 5. ...state, 6. counterClicked: true 7. } 8. ... 9. default: 10. return state; 11. } 12.}
  13. Redux Store State Component Action Reducers

  14. Time to Level Up

  15. Enhancers createStore => newCreateStore

  16. Enhancers wrap around createStore enhancer createStore

  17. 1. function enhancer (createStore) { 2. 3. 4. 5. 6.

    7. 8. 9. 10. 11. 12.} Sample Enhancer which logs on every getState()
  18. 1. function enhancer (createStore) { 2. return (...args) => {

    3. 4. 5. 6. 7. 8. 9. 10. 11. } 12.} Sample Enhancer which logs on every getState()
  19. 1. function enhancer (createStore) { 2. return (...args) => {

    3. const store = createStore(...args) 4. return { 5. ...store, 6. 7. 8. 9. 10. } 11. } 12.} Sample Enhancer which logs on every getState()
  20. 1. function enhancer (createStore) { 2. return (...args) => {

    3. const store = createStore(...args) 4. return { 5. ...store, 6. getState: () => { 7. console.log('getState was called') 8. return store.getState() 9. } 10. } 11. } 12.} Sample Enhancer which logs on every getState()
  21. How to use Enhancers 1. const store = enchancer(createStore)(reducer) OR

    1. const store = createStore(reducer, enhancer)
  22. Multiple Enhancers 1. const store = enhancer1(enhancer2(createStore))(reducer) OR 1. const

    store = compose(enhacer1, enhancer2)(createStore)(reducer) OR 1. const store = createStore(reducer, compose(enchancer1, enhancer2))
  23. ( ( ))) compose( first , second , third )(

    ...args ( )
  24. Art by Catherine Swenson - http://catherineswenson.com/

  25. Debugging – Redux Dev Tools

  26. applyMiddleware 1. export default function applyMiddleware(...middlewares) { 2. return (createStore)

    => (...args) => { 3. const store = createStore(...args) 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. } 15. }
  27. applyMiddleware is an Enhancer 1. export default function applyMiddleware(...middlewares) {

    2. return (createStore) => (...args) => { 3. const store = createStore(...args) 4. let dispatch = () => {} 5. const newStore = { 6. ...store, 7. dispatch: (...action) => dispatch(...action) 8. } 9. 10. 11. 12. 13. return newStore 14. } 15. }
  28. applyMiddleware is an Enhancer 1. export default function applyMiddleware(...middlewares) {

    2. return (createStore) => (...args) => { 3. const store = createStore(...args) 4. let dispatch = () => {} 5. const newStore = { 6. ...store, 7. dispatch: (...action) => dispatch(...action) 8. } 9. 10. const chain = middlewares.map(middleware => middleware(newStore)) // init with store 11. dispatch = compose(...chain)(store.dispatch) // load with next dispatch 12. 13. return newStore 14. } 15. }
  29. Redux Middlewares store => nextMiddleware => action => nextMiddleware(action)

  30. Redux Thunk 1. const thunk = store => next =>

    action => { 2. if (typeof action === 'function') { 3. return action(store.dispatch, store.getState) 4. } 5. 6. return next(action) 7. }
  31. Redux Thunk Usage 1. function doSomethingAction (dispatch) { 2. dispatch((dispatch,

    getState) => { 3. 4. 5. 6. 7. 8. }) 9. }
  32. Redux Thunk Usage 1. function doSomethingAction (dispatch) { 2. dispatch((dispatch,

    getState) => { 3. const state = getState() 4. dispatch({ 5. type: 'DO_SOMETHING', 6. payload: state.user 7. }) 8. }) 9. }
  33. Non DOM Side Effects 1. const setTitle = store =>

    next => action => { 2. const getState = store.getState 3. 4. const oldTitle = getState().title 5. next(action) 6. const newTitle = getState().title 7. 8. if (oldTitle !== newTitle) { 9. document.title = newTitle 10. } 11. return action 12.}
  34. Tracking Breadcrumbs / User Flow 1. import Raven from '../helpers/sentry’

    2. 3. const trackBreadCrumb = () => next => action => { 4. Raven.captureBreadcrumb({ 5. category: 'redux' 6. message: action.type 7. }) 8. return next(action) 9. }
  35. Sentry Error Logs Breadcrumbs

  36. How to use these middlewares 1. import { createStore, applyMiddleware

    } from 'redux' 2. const store = createStore( 3. reducer, 4. compose( 5. applyMiddleware( 6. thunk, 7. setTitle, 8. trackBreadcrumb 9. ), 10. window.__REDUX_DEVTOOLS_EXTENSION__() 11. ) 12. )
  37. Redux-sagas For more info attend Preeti’s talk after lunch.

  38. My Problems

  39. My Problems ^

  40. 1. Typos

  41. Typos 1. dispatch({ 2. type: 'DO_SOEMTHING_AMAMAZING' 3. })

  42. export const AMAZING_ACTION = 'AMAZING_ACTION’ // actionsList.js 1. import {AMAZING_ACTION}

    from './actionsList’ 2. 3. dispatch({ 4. type: AMAZING_ACTION 5. }) 1. import {AMAZING_ACTION} from '../actionsList’ 2. 3. export function reducer (state, action) { 4. switch (action.type) { 5. case AMAZING_ACTION: 6. ... 7. } 8. }
  43. // actionsList.js 1. export const AMAZING_ACTION = 'AMAZING_ACTION' 2. export

    const AWESOME_ACTION = 'AWESOME_ACTION' 3. export const FANTASTIC_ACTION = 'FANTASTIC_ACTION' 4. export const MINDBLOWING_ACTION = 'MINDBLOWING_ACTION' 5. export const ACTION_ACTION = 'ACTION_ACTION' 6. export const DRAMA_ACTION = 'DRAMA_ACTION' 7. export const AMAZING_ACTION_2 = 'AMAZING_ACTION_2' 8. export const AWESOME_ACTION_2 = 'AWESOME_ACTION_2' 9. export const FANTASTIC_ACTION_2 = 'FANTASTIC_ACTION_2' 10. export const MINDBLOWING_ACTION_2 = 'MINDBLOWING_ACTION_2' 11. export const ACTION_ACTION_2 = 'ACTION_ACTION_2' 12. export const DRAMA_ACTION_2 = 'DRAMA_ACTION_2' 13. export const AMAZING_ACTION_3 = 'AMAZING_ACTION_3' 14. export const AWESOME_ACTION_3 = 'AWESOME_ACTION_3' 15. export const FANTASTIC_ACTION_3 = 'FANTASTIC_ACTION_3' 16. export const MINDBLOWING_ACTION_3 = 'MINDBLOWING_ACTION_3' 17. export const ACTION_ACTION_3 = 'ACTION_ACTION_3' 18. export const DRAMA_ACTION_3 = 'DRAMA_ACTION_3’
  44. Need to add in 3 places instead of 2 1.Actions

    2.Reducers 3.ActionsList
  45. None
  46. 2. Types

  47. Types - Action 1. dispatch({ 2. type: 'ACTION_DRAMA', 3. payload:

    8 4. })
  48. Types - Action 1. dispatch({ 2. type: 'ACTION_DRAMA', 3. payload:

    { 4. id: 8, 5. rating: 9.5 6. } 7. })
  49. Types - Reducer 1. function reducer (state, action) { 2.

    switch (action.type) { 3. case 'ACTION_DRAMA': 4. const id = action.payload 5. 6. ... 7. 8. } 9. }
  50. Types - Reducer 1. function reducer (state, action) { 2.

    switch (action.type) { 3. case 'ACTION_DRAMA': 4. const {id, rating} = action.payload 5. 6. ... 7. 8. } 9. }
  51. In another location, you forgot 1. dispatch({ 2. type: 'ACTION_DRAMA',

    3. payload: 8 4. })
  52. How are you going to grep / search this? 1.

    const ACTION = 'ACTION_' 2. dispatch({ 3. type: ACTION + 'DRAMA', 4. payload: 8 5. })
  53. Image by Dave Dugdale of www.learningDSLRVideo.com

  54. None
  55. None
  56. None
  57. Action Types 1. export type DramaAction = { 2. type:

    'DRAMA_ACTION', 3. payload: { 4. id: number, 5. rating: number 6. }, 7. }; 8. 9. export type ActionAction = { 10. type: 'ACTION_ACTION', 11. payload: number, 12. }; 13. 14. export type Action = DramaAction | ActionAction;
  58. Store Types 1. import type { 2. Store as ReduxStore,

    3. Dispatch as ReduxDispatch, 4. } from 'redux'; 5. import type { Action } from './Action'; 6. import type { State } from './State'; 7. 8. export type Store = ReduxStore<State, Action>; 9. 10. export type GetState = () => State; 11. 12. export type Dispatch = ReduxDispatch<Action> 13.
  59. Action 1. import type {Dispatch} from './types/Store' 2. export function

    dramaAction(dispatch: Dispatch, id: number, rating: number) { 3. return dispatch({ 4. type: 'DRAMA_ACTION', 5. payload: { id, rating } 6. }); 7. } 8. export function actionAction(dispatch: Dispatch, id: number) { 9. return dispatch({ 10. type: 'ACTION_ACTION', 11. payload: id 12. }) 13. }
  60. Need to maintain 3 places instead of 2 1.Actions 2.Reducers

    3.ActionTypes
  61. None
  62. Too much work, must find hack. 1. export type FallbackAction

    = { 2. type: string, 3. [string]: any 4. } 5. 6. export type Action = DramaAction | ActionAction | FallbackAction;
  63. 3. Tree Shaking Removing unused code during build or minification

  64. Long list of Switch Case in Reducer 1. export default

    function reducer(state, action) { 2. switch (action.type) { 3. case 'DRAMA_ACTION': 4. return { 5. ...state, 6. movie: {id: action.id, rating: action.rating} 7. } 8. case 'ACTION_ACTION': 9. return { 10. ...state, 11. movie: {id: action.id} 12. } 13. default: 14. return state; 15. } 16. } Tree Shaking Dead Code becomes difficult
  65. 4. Route Specific Code Splitting Reducers

  66. Pinterest’s Chunks Possibly even Reducers

  67. Pinterest’s Chunks Route Specific Reducers

  68. store.replaceReducer 1. import globalReducer from './reducers/globalReducer' 2. function onRouteChange (newReducer)

    { 3. store.replaceReducer( 4. mergeReducer( 5. globalReducer, 6. newReducer 7. ) 8. ) 9. }
  69. How to go ahead with Route Specific Reducers 1. Each

    Chunk defines its reducer 2. Before component is rendered, call store.replaceReducer
  70. Problems with this approach 1. Difficult to migrate. 2. Difficult

    to ensure that the correct reducer will be available when you dispatch an action.
  71. Photo by Riccardo Annandale on Unsplash

  72. Dispatch your reducer

  73. None
  74. Dispatch your reducer instead of type 1. import {ACTION_DRAMA} from

    './dramaReducers' 2. export function dramaAction (dispatch, id, rating) { 3. dispatch({ 4. reducer: ACTION_DRAMA, 5. payload: { 6. id, 7. rating 8. } 9. }) 10.}
  75. Reducer 1. export function ACTION_DRAMA (state, payload) { 2. return

    { 3. ...state, 4. movie: { 5. id: payload.id, 6. rating: payload.rating 7. } 8. } 9. }
  76. But does it fix my problems?

  77. 1. Typos 1. import { ACTION_DRAMA } from './dramaReducers' 2.

    export function dramaAction (dispatch, id, rating) { 3. dispatch({ 4. reducer: ACTION_DRAMA, 5. payload: { 6. id, 7. rating 8. } 9. }) 10.}
  78. 2. Types 1. import type { State } from './State';

    2. 3. export type Reducer<P> = ( 4. state: State, 5. payload: P 6. ) => State; 7. 8. export type Action<P> = { 9. reducer: Reducer<P>, 10. payload: P 11. } 12. 13. export type Dispatch = <T>(action: Action<T>) => void
  79. 2. Types - Reducer 1. export function INCREMENT_COUNTER (state: State,

    payload: number): State { 2. return { 3. ...state, 4. counter: state.counter + payload 5. } 6. }
  80. 2. Types - Action 1. export function increment(dispatch: Dispatch, amount:

    number) { 2. return dispatch({ 3. reducer: INCREMENT_COUNTER, 4. payload: 1, 5. }); 6. } 7.
  81. 2. Types - IDE (Nuclide) throws errors

  82. 3. Tree Shaking Unused Reducer Code 1. import { ACTION_DRAMA

    } from './dramaReducers' 2. export function dramaAction (dispatch, id, rating) { 3. dispatch({ 4. reducer: ACTION_DRAMA, 5. payload: { 6. id, 7. rating 8. } 9. }) 10.}
  83. 3. Tree Shaking Unused Reducer Code 1. https://github.com/kentcdodds/webpack-tree-shaking-exports

  84. 4. Code Splitting across Routes - Before Entry Store Reducers

    Middlewares Component Actions
  85. 4. Code Splitting across Routes - After Entry Store Middlewares

    Component Actions Reducers
  86. How to get this to work? 1. const dispatchReducerMiddleware =

    () => next => action => { 2. if ( 3. typeof action === 'object' && 4. typeof action.reducer === 'function' 5. ) { 6. action = { 7. ...action, 8. type: action.reducer.name 9. } 10. } 11. return next(action) 12.}
  87. What about the reducer passed to createStore? 1. function reducer

    (state, action) { 2. if (action.reducer) { 3. return action.reducer(state, action.payload) 4. } 5. return state 6. }
  88. How to use with Redux Devtools 1. const store =

    createStore( 2. reducer, 3. compose( 4. applyMiddleware( 5. ...middlewares, 6. dispatchReducerMiddleware 7. ), 8. window.__REDUX_DEVTOOLS_EXTENSION__() 9. ) 10.)
  89. None
  90. combineReducers Left as an exercise to the Reader of https://github.com/azizhk/react-boilerplate/pull/1

  91. Codemod https://gist.github.com/azizhk/b4f9f5e45055a25bd28eef56540714e4

  92. Takeaway 1. Write your enhancer for your problems. 2. Write

    codemods for migrations
  93. Aziz Khambati /azizhk110 /@azizhk