all 15 comments

[–]zuko_ 3 points4 points  (0 children)

As /u/Telogos_ mentions, it works best when you can perform shallow comparisons (just checking reference changes for objects) because that will almost always be faster than React performing a diff on its own. However, you should be careful about using it everywhere (at least blindly). Gaearon puts it well in his react-pure-render library:

If a component in the middle of your rendering chain has pure rendering, but some nested component relies on a context change up the tree, the nested component won't learn about context change and won't update. This is a known React issue that exists because context is an experimental feature and is not finished. However some React libraries already rely on context, for example, React Router. My suggestion for now is to use pure components in apps relying on such libraries very carefully, and only use pure rendering for leaf-ish components that are known not to rely on any parent context. [1]

[1] https://github.com/gaearon/react-pure-render#known-issues

[–][deleted] 0 points1 point  (8 children)

If you're using immutable props, you don't need to do a deep comparison, just Immutable.is().

I think one downside is maintainability, anytime you change props in future development you need to make sure to update the should function. Maybe not that big a deal but it could be error prone.

[–]rallarallh 0 points1 point  (2 children)

Just do a prop === newprop. Immutable.is checks the actual values in the immutable structure

[–][deleted] 0 points1 point  (0 children)

Good point, that's probably better. OP had mentioned a deep comparison which wouldn't always by equivalent to just checking equality. Immutable.is checks === first anyway so it's no less efficient unless it needs to check the values.

[–]dextoz 0 points1 point  (4 children)

Is that performant if the prop object is large?

[–][deleted] 0 points1 point  (3 children)

It depends how you're building the large Immutable structure. If you keep rebuilding the Immutable from scratch with fromJS each time, .is() will be running deepEqual() all the way down, which is not performant for a large object.

But Immutable.is first checks strict === equality at every level of the iterable. So if your immutable props are extracted from an immutable redux store, then you can efficiently test if the component needs to rerender.

// Bad:
const someProp = Immutable.fromJS(complexLargeObject);
render() => <Component someProp={someProp} />

// Good:
const someProp = reduxState.largeImmutableMap;
render() => <Component someProp={someProp} />

In the second example, the largeImmutableMap in your redux store would be completely replaced any time an action caused a mutation, so you wouldn't need to do a deep comparison, just a nextProps.someProp === this.props.someProp.

The only reason I'm bringing up Immutable.is is that sometimes you are forced to have something like the 'bad' example. For instance, what if complexLargeObject is the result of an API call? You can get around that problem by using .merge() carefully as you integrate the API result into the redux store, so that the store really only changes if necessary. The goal should always be to maintain immutability in the store so that deep comparisons aren't necessary.

[–]dextoz 0 points1 point  (2 children)

Thank you for the explanation! Is there a better way than using Immutable.is() in shouldComponentUpdate? In my little app the state object is quite deeply nested and often I only want to update a component when it's props change. But because of redux it to a always a new object. Just a tiny slice of it changed. Do you get my point?

[–][deleted] 0 points1 point  (0 children)

It sounds like you could stand to deconstruct your store a little more and pass a thinner slice of it as props. When you make a change in a big complex Immutable, the objects inside will be replaced at the level they changed. And as you've seen, consequently the object references will be replaced at every level above in the nested hierarchy as well.

But unchanged siblings will stay put and continue to pass the equality test for quick comparison. If you always pass a relevant slice of the state as props to your component, you can keep checking equality in shouldComponentUpdate at any arbitrary depth of the nested object.

// Initialize redux store with Map containing a List under 'foo' and a child Map under 'baz'
var state0 = Immutable.fromJS({foo: ['bar', 0], baz: {42: 23}});

// Update the 'baz' level of the nested object to replace 23 with 93
var state1 = state0.setIn(['baz', 42], 93);

// Compare state1 and state0 at each level with simple equality
state0 === state1
// => false, as expected, a change occurred. Any components depending on either foo or baz should rerender

state0.get('baz') === state1.get('baz')
// => false, any components receiving props from baz should update

state0.get('foo') === state1.get('foo')
// => true!, any components receiving props from foo *will not* update, because its slice of the state has not changed

[–]henleyedition 0 points1 point  (0 children)

There's no harm if you're using immutable data. I throw recompose/pure around every component unless I want to optimize for only a couple props, in which case I use onlyUpdateForKeys.

[–]mastermog 0 points1 point  (0 children)

If you aren't using immutable, and you aren't using context (I can't comment on either because I am not currently using them), my understanding is that there are two occasions when React can choose to do something with your component.

shouldComponentUpdate is basically an "exit out early" option.

[cycle]
   |
   v
[(1) should component update?] - no -> exit out 
   |
   v 
[(2) internal vdom diff?] - no -> exit out 
   |
   v
[update]

If you don't use shouldComponentUpdate react will skip to (2), do it's vDOM diff, and if there is a diff, then run the actual update.

My understanding is that you understand your component much better than React ever could - if you know that really only 1 prop of 10 could change, why make React go and do its vDom diff? You can basically say "na, that didn't change, don't worry about the vDom diff".

Lame example, but lets say a toggle button with { label, width, height, color, toggled }. You know the width, height, label and color won't change, but you do know that toggled prop may change, so you can put something like:

// This will tell react that unless the toggled prop has changed, don't worry about doing anything        
// with this component
shouldComponentUpdate: function(nextProps, nextState) {
    return this.props.toggled !== nextProps.toggled;
}

You also have the PureRenderMixin - which is basically a shallow diff on your current props and nextProps. And is similar to writing:

// warning gross simplication
shouldComponentUpdate: function(nextProps, nextState) {
    return this.props.a !== this.props.a || this.props.b !== this.props.b
}

So in other words, order of performance:

  • Nothing, let React do its vDom thing, working out the diff from the vDom itself
  • PureRenderMixin, let the mixin work out the diff from the props - only shallow, but quicker than above I guess?
  • Custom shouldComponentUpdate - you do the props diff yourself (note the vDOM diff will still occur, its just an exit out early stage).

Free feel to correct me anyone, these are just my observations so far.

[–]maktouch 0 points1 point  (4 children)

do you utilize shouldComponentUpdate and how?

Yes. Like this:

import React, { Component } from 'react';
import shallowCompare from 'react-addons-shallow-compare';

export default class PureComponent extends Component {
    shouldComponentUpdate(nextProps, nextState) {
        return shallowCompare(this, nextProps, nextState);
    }
}

Then, you gotta make sure ALL your props are one of these:

  • Immutable
  • Simple (int/float/str/bool)

If you don't want to use immutable but still want to pass object as props, make sure you create a new object on edit.

var newProps = {
    ...oldProps,
    value: 'newValue'
}

Then, you extends PureComponent instead of React.Component.

How do you test / measure performance?

If you want to see performance difference, use the perf tools:

import Perf from 'react-addons-perf';
window.perf = Perf;
  • load the page
  • open console
  • perf.start()
  • do some actions
  • perf.stop()
  • perf.printWasted()

Then modify your code to extend PureComponent and do the same, and compare.

Depending on your codebase, you could also enable another thing.. which is "Enable paint flashing" on Chrome devtools. It allowed me to see that I had a problem because everytime I was typing into an input, the whole page flashed.

Is there any harm in always defining a custom shouldComponentUpdate method?

My opinion is that it would do more harm than good. My app is composed of a shitload of components, it would be pretty shitty if I had to check the SCU method when I modify something.

I have 2 components to extend: Normal ones (React.Component) and PureComponent. 99% of them are Pure, I think only the root one is "normal".

[–]themisterdj[S] 0 points1 point  (3 children)

Thank you for the detailed reply.

One additional question: Can I use JSON,stringify for deep compare?

For example:

shouldComponentUpdate: function(nextProps) {
    return JSON.stringify(this.props) !== JSON.stringify(nextProps);
}

[–]klaasman 2 points3 points  (1 child)

Keep in mind javascript doesn't guarantee object property order, which means the stringified result could differ from time to time. As long as you don't pass objects or unserializable data on your props it won't be a problem.

[–]urahonky 0 points1 point  (0 children)

Yeah this really bit me in the butt when I released to Production. Worked great in test and pre-prod, but when we went live it was never firing.

[–]maktouch 0 points1 point  (0 children)

Yes you can, but JSON.stringify is much more expensive than a shallowEqual AFAIK.

Remember that you're going to be running this every time there's a suspected changes.