React Context and Re-Renders: React Take the Wheel

A React context Provider will cause its consumers to re-render whenever the value provided changes.

// first render
<Provider value={1}>
// next render
<Provider value={2}>
// all consumers will re-render
<Consumer>{value => (/*...*/)}</Consumer>

This no big deal if you’re passing primitive values to value. If you pass 2 multiple times in a row, the consumers won’t re-render.

However, if you’re passing objects then you’ve got to be more careful. The following code will cause consumers to re-render every time the Menu renders, even if nothing in the Menu actually changed (perhaps the component that rendered Menu is changing state).

let MenuContext = React.createContext()
class Menu extends React.Component {
constructor() {
super()
this.state = {
value: this.props.defaultValue
}
}
  setValue(newValue) {
this.setState({ value: newValue })
}
  render() {
return (
<MenuContext.Provider value={{
value: this.state.value,
setValue: this.setValue
}}>
{/* other stuff */}
</MenuContext.Provider>
)
}
}

You Have Two Choices

  1. Handle mutation yourself 🤡
  2. Let React do it 😎

You should let React do it.

The only catch is that you’ll be putting something in state that bothers you. It’s okay, you’ll get over it 😬

  1. Move setValue into your state object (and then get over it 😋)
  2. Use this.state as the value to Provider.
class Menu extends React.Component {
constructor() {
super()
this.state = {
value: this.props.defaultValue,
setValue: (newValue) => {
this.setState({ value: newValue })
}
}
}
  render() {
return (
<MenuContext.Provider value={this.state}>
{/* other stuff */}
</MenuContext.Provider>
)
}
}

Now you no longer have to worry about if you changed the identify of the context you provided between renders. React took the mutation wheel.

In short: if all you ever pass to a Provider is this.state, then you’ll always render responsibly!