Redux: Re-Rendering Caused by mapDispatchToProps

I’ve worked on a couple of React/Redux projects now, and it was only recently that I realized some of the re-rendering issues I’ve run into were being caused by incorrectly using connect, and specifically the second argument to connectmapDispatchToProps.

In this post, I’ll point out the mistakes I’ve been making with connect and mapDispatchToProps, and how I’ve changed my usage to reduce unecessary re-renders.

Example Usage

Like many other developers, I looked to the Redux Documentation to get up to speed on working with Redux. Specifically, I relied on the Example: Todo List section for examples I could start with and adapt for my own purposes.

Here’s the example code for the containers/FilterLink.js file:

  
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => ({
  active: ownProps.filter === state.visibilityFilter
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)
  

As you can see, the mapDispatchToProps takes two arguments: the dispatch function (coming from Redux) and the props being passed down into this container called ownProps.

What I didn’t realize for a long time is that this code will result in a re-render every time the component receives new props—regardless of whether or not anything this component cares about changed in those props.

The mapDispatchToProps Function

The Redux API docs go into quite a bit of detail about the various ways to call connect. In describing the second argument (mapDispatchToProps), it states that it can either be an Object or a Function. And if it’s a Function (as it is in the Todo example above), it states:

If your mapDispatchToProps function is declared as taking two parameters, it will be called with dispatch as the first parameter and the props passed to the connected component as the second parameter, and will be re-invoked whenever the connected component receives new props. (The second parameter is normally referred to as ownProps by convention.)

What I’d apparently glossed over when trying to learn about connect and mapDispatchToProps was the “… and will be re-invoked whenever the connected component receives new props” part.

Just the presence of the second parameter in the function declaration is enough to cause the mapDispatchToProps function to be invoked every time the props change–even if you don’t use the props at all in your function! (Lint rules that prevent unused variables can help you avoid this, but it’s still good to understand.)

Breaking Shallow Equal

The Todo example above appears to illustrate a legitimate use case for using ownProps in a mapDispatchToProps function. But by implementing it this way, the component will now re-render every time it receives new props.

This is because every time the mapDispatchToProps function is called, it returns an object with a brand new lambda for onClick that closes over the current ownProps.filter value. That means the resulting object will never be “shallow equal” to the prior result of mapDispatchToProps (where shallow equal means that all properties of two objects are === to each other, but the top-level objects themselves aren’t necessarily ===).

Don’t Use ownProps in mapDispatchToProps

To avoid running into this issue, I’m trying to avoid using the ownProps parameter in my mapDispatchToProps functions. While it might be slightly more convenient to have your dispatch functions close over their needed props right in the mapDispatchToProps, it can be difficult to identify, and later fix, any re-render issues that this causes–especially when the component ends up wrapping components that are expensive to render.

Here’s how I’d re-write the example mapDispatchToProps from the example Todo code:

  
const mapStateToProps = (state, ownProps) => ({
  active: ownProps.filter === state.visibilityFilter
})

const mapDispatchToProps = (dispatch) => ({
  handleClick: (filter) => dispatch(setVisibilityFilter(filter))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)
  

The mapDispatchToProps no longer expects the ownProps parameter, which means the filter now needs to be passed in to the renamed handleClick function. This change requires an update to the presentation component, as well:

  
class Link extends React.PureComponent {

  onClick = () => {
    this.props.handleClick(this.props.filter);
  }

  render() {
    const { active, children } = this.props;
    return (
      <button
        onClick={this.onClick}
        disabled={active}
        style={{
            marginLeft: '4px',
        }}
      >
        {children}
      </button>
    );
  }
}
  

The caller of the handleClick function is now passing in the filter value from props. If you look at the original code for this component (components/Link.js), you’ll also note that I’ve converted it from a function to a class. I did this so I could make an onClick method that has access to props. This means that the exact same function (===) will be used in the JSX every time this component is rendered.

Shorthand Notation

A great way to enforce the rule that you never use ownProps in your mapDispatchToProps functions is to not write mapDispatchToProps functions at all! Instead, you can just pass in an object as the second argument to connect as described in the Redux API docs:

If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.

That means we could update the example one last time as follows:

  
const mapStateToProps = (state, ownProps) => ({
  active: ownProps.filter === state.visibilityFilter
})

const mapDispatchToProps = {
  handleClick: setVisibilityFilter
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)
  

Learning to pass an object instead of a function in these situations has helped me avoid unexpected re-renders. What other tricks have you found?

Conversation
  • Mark Erikson says:

    Yes – as a Redux maintainer, I _highly_ encourage that people use the “object shorthand” form, and never actually write an actual `mapDispatch` function.

    • Martijn says:

      The reason I use the mapDispatchToProps is because it is described in the examples on the Usage with React page on the redux site.

      Maybe the object shorthand should be promoted/used in the examples. That would have helped me and any new developer :-)

  • Juan Campa says:

    Hmm maybe we should add a “mapStateToDispatchProps” which would map the state to the props that are actually needed by “mapStateToDispatch”. If the result of the former doesn’t change, the latter doesn’t have to be called.

  • Jon says:

    The object shorthand is an excellent trick! Thanks for sharing!

  • estorski says:

    >The Todo example above appears to illustrate a legitimate use case for using ownProps in a mapDispatchToProps function. But by implementing it this way, the component will now re-render every time it receives new props.

    The thing is, the component always re-renders upon receiving new props anyway, even if it’s a PureComponent. So no loss in performance here.

    • estorski says:

      Unless I’ve missed the terminology here, and by “new props” you meant the reference to the “props” object itself, then it’s totally valid.

      • Patrick Bacon Patrick Bacon says:

        You said “the component always re-renders upon receiving new props anyway”… but if the “new props” are shallow equal to the “old props”, the connect function won’t pass the new props down, so the component won’t receive them / re-render.

        if the mapDispatchToProps always drops a brand new function into the props, then the old and new props will never be shallow equal, and the component will always re-render.

        • Tauyekel Kunzhol says:

          Thank you for this article! But in typical Redux app, we maintain immutability across components, which means, that no “new props” will be shallow equal to the “old props”.

          Mutating props does not make sense in Redux app.

  • Matt says:

    I noticed that using the ownProps option in mapStateToProps also causes re-renders.

  • Tad Sellers says:

    Thanks for the article. After doing some research, it turns out declaring/using ownProps in mapDispatchToProps doesn’t cause additional re-renders of the connected component. As other comments note, when ownProps changes via shallow compare, the connected component re-renders anyway, regardless of the presence of ownProps in mapDispatchToProps (in fact, regardless of the existence of a mapDispatchToProps function at all). The presence of ownProps does not increase or decrease the number of re-renderings. And keep in mind that mapDispatchToProps does not get invoked when the state changes, as mapStateToProps does. It only gets invoked on two events: 1. when the component mounts; 2. when ownProps changes, but only if you declare mapDispatchToProps as taking dispatch and ownProps rather than just dispatch. if you declare it with just dispatch, mapDispatchToProps will only be called when the component mounts (this is not explicitly stated in the Redux docs, but is implied in the docs section the article quotes: “If your mapDispatchToProps function is declared as taking two parameters, it…will be re-invoked whenever the connected component receives new props”). This means function declarations in mapDispatchToProps only get called once, so they pass the shallow compare test and don’t cause a re-render. So the advantage to not declaring ownProps in mapDispatchToProps is to avoid unnecessary calls to mapDispatchToProps, not to avoid unnecessary re-renders of the connected component.

    (I had to remind myself to beware of premature optimization: modern JavaScript engines can create tens to hundreds of millions of simple function expressions per second. But if your mapDispatchToProps performs complex calculations, refactoring can be worth it.)

    (In unusual situations, the declaration of ownProps in mapDispatchToProps will cause more re-renders than otherwise. One example is writing a mergeProps function that doesn’t pass ownProps to the connected component but does pass dispatchProps. But these situations don’t apply to the Todos example in the Redux documentation nor to most connect() functions in the wild.)

    • Tad Sellers says:

      Edit: all uses of “connected component” should be “presentational component”

    • aeronesto says:

      Very helpful. Thanks for sharing your insight.

  • John says:

    Why not drop the verbosity of mapDispatchToProps? Meaning you don’t need to use it, because dispatch is passed as a prop to the component itself. I’ve seen projects where someone passes a function reference through 6 components from a mapDispatchToProps… subsequently, changes to this are tedious and verbose. Personally I like to just connect the component (with or without state depending on the use case) to get dispatch injected as a prop. Then I can just call this.props.dispatch(myFuncCall()).

  • Comments are closed.