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 connect
— mapDispatchToProps
.
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?
The post Redux: Re-Rendering Caused by mapDispatchToProps appeared first on Atomic Spin.
Related Stories
- Building a Family Tree with GraphQL – Part 2: Adding New Resolvers
- Forcing SSL in Phoenix Framework
- Building a Family Tree With GraphQL – Part 1: Creating a Simple GraphQL API