Redux (13) bad practices Adam Klein
Why this talk?
const reducer = (state, action) => { ... const item = { ...state.item }; item.some.property = action.payload.newValue; return { ...state, item }; } Don't mutate the state
import { set } from 'lodash/fp'; const reducer = (state, action) => { ... return set( 'item.some.property', action.payload.newValue, state ); } Use immutable libraries
Use redux-freeze in development
const reducer = (state, action) => { ... const newState = deepClone(state); const item = newState.items.find(...); item.some.property = action.payload.newValue; return newState; } Don't deep clone the state
import { set } from 'lodash/fp'; const reducer = (state, action) => { ... const index = state.items.findIndex(...); return set( ['items', index, 'some', 'property'], action.payload.newValue, state ); } Use immutable libraries
const initialState = { showSpinner: false }; Don't name the UI effect
<Button disabled={ this.props.showSpinner } ...> Send </Button> Now it doesn't make sense
const initialState = { isPending: false }; Name the data
componentWillReceiveProps(nextProps) { this.setState({ filteredProducts: nextProps.products.filter(nextProps.filte }) } Don't duplicate State
const reducer = (state, action) => { ... return { ...state, products: action.payload.products, filteredProducts: action.payload.products.filter(state.filter) }; } Using Redux !== Single Source of Truth
mapStateToProps((state) => ({ filteredProducts: getFilteredProducts(state) })) const getFilteredProducts = createSelector( (state) => state.filter, (state) => state.products, (filter, products) => products.filter(filter) ) Use Selector
render() { const filteredProducts = this.getFilteredProducts(); return ( ... ); } If performance is not an issue
function userReducer(state, action) { switch (action.type) { case REQUEST_USER: return { ...state, isLoading: true }; case RECEIVE_USER: return { ...state, isLoading: false }; ... } } function jobReducer(state, action) { switch (action.type) { case REQUEST_JOB: return { ...state, isLoading: true }; case RECEIVE_JOB: return { ...state, isLoading: false }; ... } } Don't duplicate Code
function networkReducer(state, action) { switch (action.type) { case START_NETWORK: return set( [action.payload.label, 'isLoading'], true, state ) case END_NETWORK: return set( [action.payload.label, 'isLoading'], false, state ) ... } } mapStateToProps((state) => ({ isLoading: state.network.user.isLoading })) Dedicated Reducer
const reducer = (state, action) => { switch (action.type) { case SET_USER: localStorage.setItem('userSession', user.session); return { user: action.payload } } } return state; } Side Effects from Reducer
Predictable Reducers Handle side effects in: middleware storeEnhancer componentWillReceiveProps state subscription Prefer reacting to state change and not an action
this.props.getData() .then((data) => { somethingImperative(); }) Don't use promises from actions
componentWillReceiveProps(nextProps) { if (nextProps.data !== this.props.data) { somethingImperative(); } } mapStateToProps = (state) => ({ data: state.data }) React to state change
posts: { 1: { id: 1, title: 'Better Redux', comments: { 2: { id: 2, text: 'Great article!' } } } } Don't nest relational data
posts: { id: 1, title: 'Better Redux' }, comments: { id: 2, postId: 1, text: 'Great article!' } Normalized State
Normalized (flat) State Better describes relational data Easier to access all objects by ID Easier to handle and debug Performance
[ { id: 1, name: 'Kate' }, { id: 2, name: 'Jane' } ] Prefer not to use arrays
{ 1: { id: 1, name: 'Kate' }, 2: { id: 2, name: 'Jane' } } Key by ID
Tips lodash keyBy method Don't omit the ID attribute
{ products: { 1: { id: 1, name: 'Shirt', isSelected: true }, 2: { id: 2, name: 'Pants' } } } Don't mix server data with UI state
{ products: { 1: { id: 1, name: 'Shirt' }, 2: { id: 2, name: 'Pants' } }, selectedProductIds: { 1: true } } Separate UI State
const mapStateToProps = (state) => { ...state.user } Connect too much
const mapStateToProps = (state) => ({ firstName: state.user.firstName, lastName: state.user.lastName, }); // or const mapStateToProps = (state) => pick(['firstName', 'lastName'], state.user); Connect what you need
mapStateToProps = (state) => ({ currentUser: { id: state.currentUserId, role: state.currentRole } }) New Objects from mapStateToProps
mapStateToProps = (state) => ({ currentUserId: state.currentUserId, currentUserRole: state.currentRole }) Use Primitives
mapStateToProps = (state) => ({ // returns an object: currentUser: selectCurrentUser(state) }) Or Selector
const getVisibleTodos = createSelector( (state, props) => props.filter, (state, props) => state.todos, (visibilityFilter, todos) => { ... } ) const mapStateToProps = (state, props) => { return { todos: getVisibleTodos(state, props) } } Don't bust the cache
const makeGetVisibleTodos = () => { return createSelector( [ getVisibilityFilter, getTodos ], (visibilityFilter, todos) => { ... } ) } const makeMapStateToProps = () => { const getVisibleTodos = makeGetVisibleTodos() const mapStateToProps = (state, props) => { return { todos: getVisibleTodos(state, props) } } return mapStateToProps } Selector Factory
shameless promotion time
500Tech.com
angular-up.com
react-next.com (2018 - coming soon)

Redux "Bad" Practices - A List of 13 Bad Practices and How to Avoid Them

  • 1.
    Redux (13) badpractices Adam Klein
  • 2.
  • 3.
    const reducer =(state, action) => { ... const item = { ...state.item }; item.some.property = action.payload.newValue; return { ...state, item }; } Don't mutate the state
  • 4.
    import { set} from 'lodash/fp'; const reducer = (state, action) => { ... return set( 'item.some.property', action.payload.newValue, state ); } Use immutable libraries
  • 5.
  • 6.
    const reducer =(state, action) => { ... const newState = deepClone(state); const item = newState.items.find(...); item.some.property = action.payload.newValue; return newState; } Don't deep clone the state
  • 7.
    import { set} from 'lodash/fp'; const reducer = (state, action) => { ... const index = state.items.findIndex(...); return set( ['items', index, 'some', 'property'], action.payload.newValue, state ); } Use immutable libraries
  • 8.
    const initialState ={ showSpinner: false }; Don't name the UI effect
  • 9.
    <Button disabled={ this.props.showSpinner} ...> Send </Button> Now it doesn't make sense
  • 10.
    const initialState ={ isPending: false }; Name the data
  • 11.
  • 12.
    const reducer =(state, action) => { ... return { ...state, products: action.payload.products, filteredProducts: action.payload.products.filter(state.filter) }; } Using Redux !== Single Source of Truth
  • 13.
    mapStateToProps((state) => ({ filteredProducts:getFilteredProducts(state) })) const getFilteredProducts = createSelector( (state) => state.filter, (state) => state.products, (filter, products) => products.filter(filter) ) Use Selector
  • 14.
    render() { const filteredProducts= this.getFilteredProducts(); return ( ... ); } If performance is not an issue
  • 15.
    function userReducer(state, action){ switch (action.type) { case REQUEST_USER: return { ...state, isLoading: true }; case RECEIVE_USER: return { ...state, isLoading: false }; ... } } function jobReducer(state, action) { switch (action.type) { case REQUEST_JOB: return { ...state, isLoading: true }; case RECEIVE_JOB: return { ...state, isLoading: false }; ... } } Don't duplicate Code
  • 16.
    function networkReducer(state, action){ switch (action.type) { case START_NETWORK: return set( [action.payload.label, 'isLoading'], true, state ) case END_NETWORK: return set( [action.payload.label, 'isLoading'], false, state ) ... } } mapStateToProps((state) => ({ isLoading: state.network.user.isLoading })) Dedicated Reducer
  • 17.
    const reducer =(state, action) => { switch (action.type) { case SET_USER: localStorage.setItem('userSession', user.session); return { user: action.payload } } } return state; } Side Effects from Reducer
  • 18.
    Predictable Reducers Handle sideeffects in: middleware storeEnhancer componentWillReceiveProps state subscription Prefer reacting to state change and not an action
  • 19.
  • 20.
    componentWillReceiveProps(nextProps) { if (nextProps.data!== this.props.data) { somethingImperative(); } } mapStateToProps = (state) => ({ data: state.data }) React to state change
  • 21.
    posts: { 1: { id:1, title: 'Better Redux', comments: { 2: { id: 2, text: 'Great article!' } } } } Don't nest relational data
  • 22.
    posts: { id: 1, title:'Better Redux' }, comments: { id: 2, postId: 1, text: 'Great article!' } Normalized State
  • 23.
    Normalized (flat) State Betterdescribes relational data Easier to access all objects by ID Easier to handle and debug Performance
  • 24.
    [ { id: 1,name: 'Kate' }, { id: 2, name: 'Jane' } ] Prefer not to use arrays
  • 25.
    { 1: { id:1, name: 'Kate' }, 2: { id: 2, name: 'Jane' } } Key by ID
  • 26.
    Tips lodash keyBy method Don'tomit the ID attribute
  • 27.
    { products: { 1: { id:1, name: 'Shirt', isSelected: true }, 2: { id: 2, name: 'Pants' } } } Don't mix server data with UI state
  • 28.
    { products: { 1: { id:1, name: 'Shirt' }, 2: { id: 2, name: 'Pants' } }, selectedProductIds: { 1: true } } Separate UI State
  • 29.
    const mapStateToProps =(state) => { ...state.user } Connect too much
  • 30.
    const mapStateToProps =(state) => ({ firstName: state.user.firstName, lastName: state.user.lastName, }); // or const mapStateToProps = (state) => pick(['firstName', 'lastName'], state.user); Connect what you need
  • 31.
    mapStateToProps = (state)=> ({ currentUser: { id: state.currentUserId, role: state.currentRole } }) New Objects from mapStateToProps
  • 32.
    mapStateToProps = (state)=> ({ currentUserId: state.currentUserId, currentUserRole: state.currentRole }) Use Primitives
  • 33.
    mapStateToProps = (state)=> ({ // returns an object: currentUser: selectCurrentUser(state) }) Or Selector
  • 34.
    const getVisibleTodos =createSelector( (state, props) => props.filter, (state, props) => state.todos, (visibilityFilter, todos) => { ... } ) const mapStateToProps = (state, props) => { return { todos: getVisibleTodos(state, props) } } Don't bust the cache
  • 35.
    const makeGetVisibleTodos =() => { return createSelector( [ getVisibilityFilter, getTodos ], (visibilityFilter, todos) => { ... } ) } const makeMapStateToProps = () => { const getVisibleTodos = makeGetVisibleTodos() const mapStateToProps = (state, props) => { return { todos: getVisibleTodos(state, props) } } return mapStateToProps } Selector Factory
  • 36.
  • 37.
  • 38.
  • 39.