Overthinking React Redux

When I'm learning a new technology I inevitably overthink it at some point. Good libraries work the way you naively expect, even if there's some magic under the hood to make that true. There comes a point where I understand enough about a library to start questioning my naive expectations because I don't know enough to understand the magic. Of course the best way to deal with this situation is to test the behaviour, and then, thanks to open sources, dig into how it achieves that magic to understand a bit more about the technology.

I've just been going through this cycle with React Redux, specifically the question of Can I nest a connected component within another connected component?

As you might expect naively (or by reading the React Redux FAQ) the answer is yes. But as is often the case, the journey is more interesting than the destination. I learned a lot while working out why the answer is yes.

Why is this even a question?

What first started me thinking was this comment in the Redux docs:

React Redux library's connect() function, which provides many useful optimizations to prevent unnecessary re-renders. (One result of this is that you shouldn't have to worry about the React performance suggestion of implementing shouldComponentUpdate yourself.)

The referenced React performance optimisation is that by implementing the shouldComponentUpdate method on a component (or by extending PureComponent) you can prevent React spending time re-rendering components where the properties or state have not changed. I assumed (correctly as it turns out) that React Redux implements shouldComponentUpdate for you so that the component is only re-rendered if it's properties, or the Redux state it's bound to, changes.

In fact, the React Redux FAQ says this explicitly:

React Redux tries to improve performance by doing shallow equality reference checks on incoming props in shouldComponentUpdate, and if all references are the same, shouldComponentUpdate returns false to skip actually updating your original component.

This is where I started to go down the rabbit hole, the react docs indicate that when shouldComponentUpdate returns false it not only prevents the current component re-rendering, but the whole sub-tree. So React doesn't even check any child components to see if they need to be updated.

So the confusing point: If Connected components don't re-render when the state hasn't changed, and preventing re-rendering also prevents re-rendering of a child component, won't nesting connected components prevent the inner component from updating when only the inner component state changes?

So how does it work?

A quick test shows that nested connected components do indeed work, so there's clearly something I'm missing. Turns out the missing piece is clearly mentioned in thee React Docs

Note that returning false does not prevent child components from re-rendering when their state changes.

I only found while writing this up, really must remember to read the docs before working things out the hard way. But in retrospect it's kind of obvious that changing a components state causes it to update. I just don't tend to think about React state because I'm using Redux for state.

But that still leaves the question, given that I'm not using React state, why is the nested component updating. Turns out the answer is surprisingly boring once I know what to look for. When React Redux determines that the relevant Redux state has changed, it sets dummy state no the componen which causes the component to update even though a parent component returned false from shouldComponentUpdate.

More worrying about purity

While working all this out I did a lot of reading on pure components to make sure I understood what was going on. One of the gotchas with pure components is "Don’t bind values in functions in render". i.e.:

<CommentItem likeComment={() => this.likeComment(user.id)} />

The problem is that every time the parent’s render method is called, a new function (with a new reference) is created to be passed to likeComment. This has the side effect of changing the props on each child which in turn will cause them all to re-render, even if the data itself is all the same.

Overthinking it again I wondered how this affects connected components. After all, a typical mapDispatchToProps implementation does create new functions. e.g. this example from the react redux docs.

const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}

So won't this mean the properties change every time, meaning the component always re-renders? Once again it turns out the redux developers thought this through a lot better than I did. Connected components have multiple levels of "did it change" checks to prevent these situations:

If the state is different, then mapStateToProps is called, but mapDispatchToProps is not called since it can't rely on state, instead the same actions from last time are used. Which prevents the re-renders I was worried about.

If the props passed to the connected component change both mapStateToProps and mapDispatchToProps might be called, but react redux is smart enough to only do this if these functions accept the ownProps argument.

So in practice changing the props passed to the connected component will almost always cause a re-render, state changes will only cause a re-render if the properties actually extracted from the state change, and the mapping functions (mapStateToProps, mapDispatchToProps) will only be called if their inputs change.

Or to put it another way, react redux is really lazy (in a good way) and really does trigger the least amount of change it can to reflect the changes in props and state.

Stateless components and pure components

The other thing the kept coming up in my research were blogs about how stateless functional components are not actually pure and this is bad and confusing

In summary, stateless functional components are not pure (in the react sense) so re-render even if the properties haven't changed just in-case a child component is not pure. Much of the advise is to avoid stateless functional components and instead always subclass Component or PureComponent so you can optimise by implementing shouldComponentUpdate.

This is a shame, because I find that functional components are really nice for writing presentational components to connect to redux.

Happily all the above optimisations mean this advice is unnecessary when it comes to connected components, the connected component is pure and optimised regardless of how the wrapped component behaves.

The shocking conclusion!

So this was all a really long way of saying "react redux does what it says on the tin". It really does optimise quite aggressively so that components are only re-rendered when they need to be, but equally they are re-rendered every time they need to be.

So really just use connected components with functional components and don't over think it.

Comments !

social