- more like
shouldComponentRerender()
- never
PureComponent
a component withchildren
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.
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() {
//...
}
}
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 PureComponent
s.
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 ashouldComponentRerender
.
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 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...
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 PureComponent
s with children
though.
New rule: Never
PureComponent
a component with achildren
prop.PureComponent
s with subcomponents in their render are still OK though.
See this github issue for more.
- Optimizing Performance - React
- React PureComponent Pitfalls – ShakaCode
- Take
children
offprops
· Issue #4694 · facebook/react - Optimizing React Rendering (Part 1) – Flexport Engineering
- Why Did This React Component Re-render? by Eric Lathrop
- React is Slow, React is Fast: Optimizing React Apps in Practice – DailyJS
- "React Q: Should we always use PureComponent and a pure "mixin" for SFC? Or bc most components are not costly to render it's better not?"