Why isn’t my React component re-rendering?

This article separates errors that are common to plain React, React and Redux, and React-Redux.

Plain React: this is the simplest error to make, but I’ve seen it happen many times.

  • Are you manually reassigning state, using: `this.state = newState` instead of: `this.setState(newState)`?
  • Though manually reassigning or mutating the state will seem to update the state, it will not trigger a re-render of the component, and it should never be done.

React with Redux: if you are using Redux, make sure that you are making the most of the Redux devTools! Before looking too much at your React component, make sure that the right actions are being dispatched and that the new store state is correct. If the store state is updating, but your component isn’t re-rendering, then look for these errors:

  • Does your Redux store mutate state instead of returning a new state object that has a different pointer? React uses shallow equality to check for changes to the state, so mutating the state will not trigger re-rendering. Use Object.assign or Rest with Object properties to avoid this error.
  • Even when you return a new state object, you also need to make sure that you aren’t mutating any arrays or objects that are nested within the state. It’s preferred style to keep your state for each Reducer small (absolutely no nested objects or arrays) and use combineReducers liberally. It’s very easily to accidentally mutate state when there is even just one level, because Javascript does not have a particularly clear distinction between array methods that return a new array and array methods that mutate the array in place. You could also solve this problem by using recursive duplication (like lodash.merge) or with immutable.js, but this is likely to eventually hurt performance and cause unnecessary re-rendering.

A subset of this problem is: Nested Entities and Eager Loading with an ORM.

  • If you are using an ORM on your backend that supports eager loading (like Sequelize or Active Record), and making AJAX requests for these resources and adding them to your redux store (with THUNKs), you are probably creating duplicate/nested entities on your Redux store — potentially without even realizing it. Any action that updates this field needs to both change the entity itself, and where it exists as a property on another model. Redux documentation suggests to keep models separate in our store just as they are separate in our database.
  • If you want to use eager loading, make your action to update that data match cases on multiple reducers — one to update that data itself, and another to update wherever that model is eagerly loaded. However, this could end up causing unnecessary re-rendering.
  • The Redux Docs recommend using the Normalizr library to create a normalized transformation the nested data before updating the store.

Using React-Redux library to subscribe to the store:

  • The points above about shallow equality for state still apply, and we add another concern:
  • If using React-Redux, does your mapStateToProps function send all of the relevant state down? If you aren’t subscribed to it with mapStateToProps, it won’t trigger a re-render.

Worst case scenario: If you really can‘t make your component re-render, you can always force an update using forceUpdate(), which will call the render function and all of the other lifecycle methods (except shouldComponentUpdate). This should generally be avoided, because React will trigger appropriate re-rendering if state and props are being managed correctly.