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

Redux Deep Dive - Dispatch your Reducer - React...

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

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

Aziz Khambati

January 19, 2018
Tweet

Other Decks in Technology

Transcript

  1. 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. )
  2. Agenda 1. Redux Basics (Brief) 2. Enhancers, Middlewares 3. Problems

    1. Typos, Types, Tree Shaking, Chunking 2. Partial but failing Solutions 4. Solution
  3. 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. };
  4. 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);
  5. 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. }
  6. 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.}
  7. 1. function enhancer (createStore) { 2. 3. 4. 5. 6.

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

    3. 4. 5. 6. 7. 8. 9. 10. 11. } 12.} Sample Enhancer which logs on every getState()
  9. 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()
  10. 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()
  11. How to use Enhancers 1. const store = enchancer(createStore)(reducer) OR

    1. const store = createStore(reducer, enhancer)
  12. 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))
  13. 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. }
  14. 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. }
  15. 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. }
  16. 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. }
  17. 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. }
  18. 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.}
  19. 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. }
  20. 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. )
  21. 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. }
  22. // 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’
  23. 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. }
  24. 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. }
  25. How are you going to grep / search this? 1.

    const ACTION = 'ACTION_' 2. dispatch({ 3. type: ACTION + 'DRAMA', 4. payload: 8 5. })
  26. 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;
  27. 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.
  28. 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. }
  29. 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;
  30. 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
  31. store.replaceReducer 1. import globalReducer from './reducers/globalReducer' 2. function onRouteChange (newReducer)

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

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

    to ensure that the correct reducer will be available when you dispatch an action.
  34. 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.}
  35. 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. }
  36. 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.}
  37. 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
  38. 2. Types - Reducer 1. export function INCREMENT_COUNTER (state: State,

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

    number) { 2. return dispatch({ 3. reducer: INCREMENT_COUNTER, 4. payload: 1, 5. }); 6. } 7.
  40. 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.}
  41. 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.}
  42. 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. }
  43. 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.)