Skip to content

Latest commit

 

History

History
289 lines (231 loc) · 9.55 KB

shouldComponentUpdate.md

File metadata and controls

289 lines (231 loc) · 9.55 KB

All about React's shouldComponentUpdate()

TL;DR

  • more like shouldComponentRerender()
  • never PureComponent a component with children

A brief introduction

By default, when a component changes and re-renders, all components in its render() are also re-rendered, and their subcomponents are re-rendered, and so on, all the way down. shouldComponentUpdate() is a class-based lifecycle method that empowers developers to change the default of a component, and prevent re-rendering completely, or only enable it in certain cases.

class MyComponent extends React.Component {
  /**
  * Default shouldComponentUpdate when not defined
  *
  * shouldComponentUpdate() {
  *   return true
  * }
  */

  render() {
    //...
  }
}

Note: this hook is only available to class-based components, not Stateless Functional Components.

Why do we care about limiting re-renders?

Performance!

The less code we run, the more performant it'll be. In fact, the most performant thing we could do in our React apps today would be to write all of our components with a shouldComponentUpdate() { return false }. Of course, that is just about as helpful as saying "the most secure computer is the one that is turned off, and buried 100 feet under ground". Our app would be completely static and non-interactive if all of our components had a sCU => false.

There are some components that really should be static and non-interactive though. Once rendered the first time, they should live the rest of their time without updating and re-rendering. This is where we'll want to sCU => false.

There are also components that should re-render if and only if certain (or any) props have actually changed. This is where custom shouldComponentUpdate() and React.PureComponent come in.

class CustomShouldComponentUpdate extends React.Component {
   shouldComponentUpdate(nextProps, nextState) {
     return nextProps.trigger !== this.props.trigger
   }

  render() {
    //...
  }
}

PureComponent

In our class-based components, we can extend React.PureComponent instead of React.Component to only re-render if a prop or state has changed. It essentially inherits a shouldComponentUpdate that checks the current props and state with the new ones.

class MyPureComponent extends React.PureComponent {
  /**
  * Default PureComponent shouldComponentUpdate
  *
  * shouldComponentUpdate(nextProps, nextState) {
  *   // essentially (this.props !== nextProps || this.state !== nextState)
  *   return shallowCompare(this, nextProps, nextState)
  * }
  */

  render() {
    //...
  }
}

Be careful with the equality checks of functions, objects, and arrays though. You'll want to use their references (not creating new ones in render()s) and consider using an immutable lib for deep equality checks.

Note: Some Stateless Functional Components (not class-based) may be "pure" in the sense that they do not have side effects, but they will always re-render, so don't confuse them as PureComponents.

OK, intro over. Lets dig in!

Should be renamed to shouldComponentRerender?

Given this sCU => false component,

class NoUpdate extends React.Component {
  shouldComponentUpdate() {
    return false
  }

  componentWillReceiveProps(nextProps) {
    console.log('cWRP: ', nextProps)
  }

  render() {
    console.log('render: ', this.props)
  }
}

and this component that just updates the prop it passes to the above component after a timeout,

class UpdatesChildProp extends React.Component {
  state = {
    updates: 'initial',
  }

  componentDidMount() {
    setTimeout(() => this.setState({updates: 'updated'}), 400)
  }

  render() {
    return (
      <NoUpdate updatingProp={this.state.updates} />
    )
  }
}

ReactDom.render(<UpdatesChildProp />)

what do you expect to see in console?

render: {updatingProp: "initial"}
cWRP: {updatingProp: "updated"}

What does this mean? sCU => false will not re-render, but its props will still update.

So really, it is best to think of shouldComponentUpdate as more of a shouldComponentRerender.

Note: this is actually a pretty cool thing! One use case where we don't want to re-render a component, but still respond to an updated prop is for enabling a declarative prop for an imperative operation. An <Animate> component, for example, might accept a triggerOnChangedValue prop, which will call an .animate() method. Using this component will not re-render, it'll just animate() when the prop changes: <Animate triggerOnChangedValue={this.state.animationTrigger} />.

When to avoid PureComponent

When Dan Abramov tweeted "PSA: React.PureComponent can make your app slower if you use it everywhere.", he was referring to PureComponent's shouldComponentUpdate() executing code for each update. If a component is re-rendered more often that it is prevented, then it is wastefully executing that code. As mentioned in the intro, this'll happen when passing in new objects, arrays, and functions, but there is another case...

Exploring shouldComponentUpdate and children

Let's start with some controls. Given this page, what do we expect to see in console?

const Div = (props) => {
  console.log('Div Render');

  return (
    <div>{props.children}</div>
  )
}

export default class Home extends React.Component {
  state = {}

  componentDidMount() {
    setTimeout(() => this.setState({hi: 'hi'}), 2000)
  }

  render() {
    return (
      <Div>{this.state.hi}</Div>
    )
  }
}
Div Render # initial mount

Div Render # after setState

Given this added code, what do we expect to see in console?

class WrappingWontUpdate extends React.Component {
  shouldComponentUpdate() {
    return false
  }

  render() {
    console.log('WrappingWontUpdate Render');
    return this.props.children
  }
}

export default class Home extends React.Component {
  //...

  render() {
    return (
      <WrappingWontUpdate>
        <Div>{this.state.hi}</Div>
      </WrappingWontUpdate>
    )
  }
}
WrappingWontUpdate Render # initial mount
Div Render # initial mount

# nothing after setState

Makes sense, makes sense. Let's explore PureComponent.

class PureComponentWrapper extends React.PureComponent {
  render() {
    console.log('PureComponentWrapper Render');
    return (
      <div>{this.props.children}</div>
    )
  }
}

export default class Home extends React.Component {
  //...

  render() {
    return (
      <PureComponentWrapper>
        <Div>{this.state.hi}</Div>
      </PureComponentWrapper>
    )
  }
}
PureComponentWrapper Render # initial mount
Div Render # initial mount

PureComponentWrapper Render # after setState
Div Render # after setState

Hmmm, we have a wrapping PureComponent, but still its update is run. Ah, this must be because the child is updated. So what happens when we try this render?

<PureComponentWrapper>
  <WontUpdate />
</PureComponentWrapper>
PureComponentWrapper Render # initial mount
WontUpdate Render # initial mount

PureComponentWrapper Render # after setState

Uhhh, what? Why is PureComponentWrapper re-rendering even when the child hasn't changed? Does PureComponent not actually do anything?

One more exploration:

class PureComponentWithSubcomponents extends React.PureComponent {
  render() {
    console.log('PureComponentWithSubcomponents Render');

    return (
      <span>
        <Div />
      </span>
    )
  }
}

export default class Home extends React.Component {
  state = {}

  componentDidMount() {
    setTimeout(() => this.setState({hi: 'hi'}), 2000)
  }

  render() {
    return (
      <PureComponentWithSubcomponents />
    )
  }
}
PureComponentWithSubcomponents Render # initial mount
Div Render # initial mount

# nothing after setState

Yes, the control above shows that PureComponent does indeed work as expected when there are no children. Note that this PureComponent does have subcomponents in its render, but does not have children passed in via the parent.

(Neat, this exploration helped me identify a distinction between children and subcomponents! Children of my component are passed in by my parent (they exist inside my parent’s render). Subcomponents exist in my render.)

Still a bummer that we can not benefit from PureComponents with children though.

New rule: Never PureComponent a component with a children prop. PureComponents with subcomponents in their render are still OK though.

See this github issue for more.

Other reads