PureComponent's shallowCompare is not working as expected - reactjs

I am trying to avoid unnecessary re-rendering of a child component (Review) by extending it from PureComponenet.
This component is being called from the parent component (Reviews), which is not a PureComponent.
By debugging I am seeing that whenever a change occurs to one of the review, all the reviews related to that product are getting re-rendered. Obviously Reviews' props gets updated and it passes individual Review's relevant props to individual Review components.
where I am also seeing this,
nextProps.reviewer === this.props.reviewer_info -> false
nextProps.reviewer_info = []
this.props.reviewer_info = []
I believe that pureComponent uses shallowCompare and it should return true while comparing 2 empty arrays, is that correct?
Does my parent component also needs to be a PureComponent in order to stop unnecessary rendering of my child components? Is that the missing part?
My review component has other child components, which I know also should be declared pureComponents. (I will update them but does this stop Review component from achieving pureComponent's benefits?)
Or above 2 are not the correct reasons for the re-rendering in-spite of using pureComponent and I am missing some other part?

Does my parent component also needs to be a PureComponent in order to stop unnecessary rendering of my child components?
No, that's not necessary.
I believe that pureComponent uses shallowCompare and it should return true while comparing 2 empty arrays, is that correct?
It is not. The shallow comparison uses === to compare the old and new value of each property on props and state. But as you can see in a browser console [] === [] is false. This is because each [] expression creates a new array object that is unequal to the others.
As a consequence, a simple array or object literal in a property (e.g. <Review prop={[]}/>) will trigger a rerender of Review whenever Reviews is rendered, negating the effect of the PureComponent.
To enjoy the optimized behavior, you will need to make sure that each non-primitive prop keeps referring to the same object instance as long as its value doesn't change (e.g. by keeping it in Review's state). Furthermore, if there is a change, you will need to create a fresh top-level object for that prop, rather than modify the existing one in-place (so don't use push or assign directly to the prop objects properties.) The immutable-js package can be helpful with this, but es6 spread operators often work fine as well.
My review component has other child components, which I know also should be declared pureComponents. (I will update them but does this stop Review component from achieving pureComponent's benefits?)
It shouldn't affect Review (unless you create the children in the render method of Reviews, like <Review ..>..<Child ../>..</Review>, in which case props.children will change every time and trigger rerendering.)

You can use shouldComponentUpdate() alternatively if your prop/state is an array:
shouldComponentUpdate(nextProps, nextState) {
return JSON.stringify(this.props.reviewer_info) !== JSON.stringify(nextProps.reviewer_info);
}

Related

How to only update one element within a component without rerendering the whole component?

I'm trying to optimize the performance of app. I've found that the issue is that one of my components re-renders on a condition that I set and it contains a significant amount of data. I wanted to know if it were possible to not re-render the whole component, but to only re-render the element that has changed?
I've already tried using PureComponent, but that did not have enough control for me. I'm currently using shouldComponentUpdate.
By default every time a parent component is re rendered all the children nodes will be rendered as well, if you know for sure that a given component will always render the same content given the same props then you could turn it into a PureComponent, a pure component performs a shallow comparison obj1 === obj2 in props and only re-render when a change occurs. If you need a more complex comparison logic then shouldComponentUpdate is the way to go:
shoudComponentUpdate(prevProps, prevState){
if(prevProps.items.length !== this.props.items.length) return true
return false
}
By default this method always returns true, unless you explicitly tells it not to. The functional version of shouldComponentUpdate is React.memo. But keep in mind that comparisons take time, and the amount of processing required to perform a comparison grows with the complexity. Use it carefully.
I would suggest breaking out the piece that needs to be updated into it's own component and use React.memo or PureComponent like you were saying. This might also help.
React partial render

React will not create a new nested component as expected

In my application I have a page change mechanism that uses one of the properties of parent component state.
So I do something like:
class mainComponent{
state={
mypage:null
}
onclickhandler(page){
this.setState({mypage:page});
}
render(){
return <div>{page}</div>
};
}
In my "page" property I have various input fields and other components.
When I change the page, all inputs get updated with new values. However, my custom nested components are never re-created.
Instead, reach just calls componentDidUpdate on them.
Is there a way to force it to construct a new nested component somehow?
React won't recreate component when it's not needed - it's a part of optimizations and general way react works.
When 'new', 'expected' component exists (the same type, the same or no key id) it only receives updated props.
Updating props sometimes doesn't force rerendering (it's guaranteed for state updates). PureComponent compares props shallowly, you can check shouldComponentUpdate (should return true to force render).

Does React perform a deep compare or a shallow compare in render method?

React Document says
React.PureComponent's shouldComponentUpdate() only shallowly compares
the objects.
Does it mean the Component will do a deep compare unless we make it a PureComponent ?
No, by default Component will re-render even if its props stay the same (by doing no compare at all), unless you decide to implement your own shouldComponentUpdate.
From docs:
render() will not be invoked if shouldComponentUpdate() returns false.
and then:
shouldComponentUpdate() is invoked before rendering when new props or
state are being received. Defaults to true.
Because of missing rep i will add this as a comment above from pwolaq:
You can really take advantage of using PureComponent when you use an immutable data structure as your app's data. When an immutable data structure is changed, we get a different object. Thats the reason a shallow compare (which is very cheap to process) will quickly detect changes.
Using redux in conjunction with seamless-immutable for example lets you build applications that only rerender specific components bound to data that is nested in a deep application data object are bound to updated without ever having to implement shouldComponentUpdate()

ReactJS, Calling setState with same parameter

I have been reading the React docs and came across shouldComponentUpdate(). My understanding is that everytime setState() is called, a re-render of that component will be updated.
My question is that If the value to be updated is the SAME as the current state value, would this trigger a re-render event? or I would have to manually checks for the current value and value to be updated in shouldComponentUpdate()
The official React documentation states:
The default behavior is to re-render on every state change...
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
This means that by default, render() will be executed if any of a component's state or props values changes.
You can override this default behavior using shouldComponentUpdate(). Here's an example that only updates if a state value changes.
shouldComponentUpdate(nextProps, nextState) {
return this.state.someValue !== nextState.someValue;
}
Note: this example completely ignores props. So, any changes to props will not trigger render().
Adding more to #Jyothi's answer regarding implementing shouldComponentUpdate() to skip unnecessary re-renders, in React 15.3 they introduced a new concept PureComponent. From reactjs docs
The difference between them is that React.Component doesn’t implement
shouldComponentUpdate(), but React.PureComponent implements it with a
shallow prop and state comparison.
This allows to skip unnecessary calls of render in class components by just implementing PureComponent instead of the usual Component. There are a few caveats with PureComponent though, from the docs about React.PureComponent’s shouldComponentUpdate():
... only shallowly compares
the objects. If these contain complex data structures, it may produce
false-negatives for deeper differences.
... skips prop updates for the whole component subtree. Make sure all the
children components are also “pure”.
Usage of PureComponent can in some cases improve performance of your app. Moreover, it enforces you to keep state and props objects as simple as possible or even better, immutable, which might help simplify the app structure and make it cleaner.
I dont know if I understood your question correctly but react only re renders when there is difference between virtual dom and real dom.
And as Jyothi mentioned in his answer that render method will be called irrespective of the value passed in the set state function but rerendering will depend on what this render method returns.
In functional components, calling setState() with the equal value won't cause a rendering, while in a class component it does: https://dev.to/sunflower/reactjs-if-it-is-setting-a-state-with-the-same-value-will-the-component-be-re-rendered-5g24
Note that we're just talking about virtual (React) renderings here. Brower-rendering won't happen in any case - i.e. neither in the functional component nor in the class component - as long as the state (or to be more precise: the effective DOM) doesn't change.

React: Parent component re-renders all children, even those that haven't changed on state change

I haven't been able to find a clear answer to this, hope this isn't repetitive.
I am using React + Redux for a simple chat app. The app is comprised of an InputBar, MessageList, and Container component. The Container (as you might imagine) wraps the other two components and is connected to the store. The state of my messages, as well as current message (the message the user is currently typing) is held in the Redux store. Simplified structure:
class ContainerComponent extends Component {
...
render() {
return (
<div id="message-container">
<MessageList
messages={this.props.messages}
/>
<InputBar
currentMessage={this.props.currentMessage}
updateMessage={this.props.updateMessage}
onSubmit={this.props.addMessage}
/>
</div>
);
}
}
The issue I'm having occurs when updating the current message. Updating the current message triggers an action that updates the store, which updates the props passing through container and back to the InputBar component.
This works, however a side effect is that my MessageList component is getting re-rendered every time this happens. MessageList does not receive the current message and doesn't have any reason to update. This is a big issue because once the MessageList becomes big, the app becomes noticeably slower every time current message updates.
I've tried setting and updating the current message state directly within the InputBar component (so completely ignoring the Redux architecture) and that "fixes" the problem, however I would like to stick with Redux design pattern if possible.
My questions are:
If a parent component is updated, does React always update all the direct children within that component?
What is the right approach here?
If a parent component is updated, does React always update all the direct children within that component?
No. React will only re-render a component if shouldComponentUpdate() returns true. By default, that method always returns true to avoid any subtle bugs for newcomers (and as William B pointed out, the DOM won't actually update unless something changed, lowering the impact).
To prevent your sub-component from re-rendering unnecessarily, you need to implement the shouldComponentUpdate method in such a way that it only returns true when the data has actually changed. If this.props.messages is always the same array, it could be as simple as this:
shouldComponentUpdate(nextProps) {
return (this.props.messages !== nextProps.messages);
}
You may also want to do some sort of deep comparison or comparison of the message IDs or something, it depends on your requirements.
EDIT: After a few years many people are using functional components. If that's the case for you then you'll want to check out React.memo. By default functional components will re-render every time just like the default behavior of class components. To modify that behavior you can use React.memo() and optionally provide an areEqual() function.
If a parent component is updated, does React always update all the direct children within that component?
-> Yes , by default if parent changes all its direct children are re-rendered but that re-render doesn't necessarily changes the actual DOM , thats how React works , only visible changes are updated to real DOM.
What is the right approach here?
-> To prevent even re-rendering of virtual DOM so to boost your performance further you can follow any of the following techniques:
Apply ShouldComponentUpdate Lifecycle method - This is applied only if your child component is class based , you need to check the current props value with the prev props value ,and if they are true simply return false.
Use Pure Component -> This is just a shorter version to above method , again works with class based components
Use React memo -> this is the best way to prevent Rerendering even if you have functional components ,you simply need to wrap your components export with React.memo like : export default React.memo(MessageList)
Hope that helps!
If parent component props have changed it will re-render all of its children which are made using React.Component statement.
Try making your <MessageList> component a React.PureComponent to evade this.
According to React docs: In the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component. check this link for more info
Hope this helps anyone who is looking for the right way to fix this.
If you're using map to render child components and using a unique key on them (something like uuid()), maybe switch back to using the i from the map as key. It might solve the re-rendering issue.
Not sure about this approach, but sometimes it fixes the issue

Resources