Avoid re-rendering a component which contains children props - reactjs

How to avoid re-rendering a component which is having children props. React.memo seems to be working only on regular props
const FormGroup = ({
children
}) => {
return (
<div>
{children}
</div>
)
}
export default React.memo(FormGroup)

React.memo takes a second argument which lets you control how and which props are compared.
function FormGroup(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
return true;
}
export default React.memo(FormGroup, areEqual);
Note from docs:
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.

React.memo and React.PureComponent both cannot optimize defaultly if children prop is passed. That is because on each re-render of parent a new instance of Children prop is created and hence the reference is lost.
This is explained in details in this github issue
Also as Dan Abramov mentioned in one of the comments within the the github link
You can choose to implement a custom shouldComponentUpdate for React.Component or an areEqual for React.memo. However doing this may often be an overhead and not necessarily be more beneficial than a render itself
Also render is cheap and fast and it okay to have a re-render for a simple component

Related

How to prevent re-render of entire list on addition of new element in React

I would like to render only newly added elements instead of re-rendering the entire list when a new element gets added to the list. I tried adding key prop but still all elements are rendering.
This is an example code for my use-case https://codesandbox.io/s/jovial-poincare-qcsvn?file=/src/App.js, here we can see console log for all list elements when we add a new element to the list.
It happens because you defined your CommentView component inside the App Component
any update on the state cause the component to be re-render and react treats these CommentView recreated version as it is a completely different component lets assume react think you change the div to span it has no idea they are the same old component
Hoist the component outside the App component
const CommentView = React.memo(/**/)
const App = () => {/* rest without the CommentView definition */}
you can also use Pure component instead of memo if you want, which compare shallowly
class CommentView extends PureComponent {
render() {
const comment = this.props.comment;
return (
<h1 key={`comment-${comment.id}`}>
{console.log("Rendering", comment.id)}
{comment.id}: {comment.msg}
</h1>
);
}
}
export default function App() {
// rest of code
First of all, your id is not unique. If you add this, it will be unique:
{
id: comments[comments.length - 1].id + 1,
msg: "C"
}
Changing the state means that React triggers an update when we call the setState function (in React hooks, you would use useState). This doesn't only mean the component's render function will be called, but also that all its subsequent child components will re-render, regardless of whether their props have changed or not.
React.memo only does the shallow comparison.
So you need to supply your own comparison logic as a second parameter.
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);

How to know if props have changed when using React.memo() with a functional Component

I need to make an equivalent for the obsolete shouldComponentUpdate react lifecycle method with react hooks.
I have a Functional Components as it's called in this article which uses React.memo() to do it.
I just couldn't understand how to prevent the re-rendring of the component if only props is returned as a unique argument.
In others React.memo() examples (as in here) there is also a nextProps which is returned besides props and which is useful to compare with props to decide wether to let re-render the component or not.
Even when i add a second argument to be returned by the component , i always got just an empty plain js object.
Such as Functional Components aren't they always stateless so they won't keep any of the component's variables's values even those instanciated with the React.useState() ? So how we are supposed to know if props have changed ?!
this is the component:
import React, { useEffect, useRef, useState, memo } from "react";
const TinyEditor = (props, nextProps) => {
return 'some jsx'
};
const MemoizedTinyEditor = memo(
TinyEditor,
(props, nextProps) => {
// if(props.prop1 === nextProps.prop1) {
// // return true if you don't need re-render
// }
console.log("TinyEditor :: MEMO!!!!!!!!!!");
return true;
}
);
export default MemoizedTinyEditor;
Have you tried what's mentioned in react's docs ?
By default it will only shallowly compare complex objects in the props
object. If you want control over the comparison, you can also provide
a custom comparison function as the second argument.
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
React.memo
If you have tried this.. you should add attempted code to your question for others to review and figure out the issue. FYI this only works for props.. if the state of a component changes.. it will always rerender

Does React renders Component due to props?

According to that link: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
render() may be triggered with new props. Could someone give me a code example for that? I cannot see how props change invoke rendering! Please not by changing the props via the state; then it is setState() that invokes render()...
Look at shouldComponentUpdate() - this is it's signature - it returns a boolean. Props is there so you can compare and manually say whether the component should update.
shouldComponentUpdate(nextProps, nextState)
For function components React.memo is the replacement for shouldComponentUpdate which is used with class components.
const myComponent = React.memo(props => {
...
code of React.FunctionComponent
...
},
(prevProps, nextProps) => prevProps.propA === nextProps.propA
);
React.memo gets two arguments shown above: a React.FunctionComponent which will be wrapped around by memo and an optional function that returns a boolean.
When the function returns true, the component will not be re-rendered. If the function is omitted then its default implementation in React.memo works like the implementation of shouldComponentUpdate in React.PureComponent. E.g. it does shallow comparison of props, the difference is that only props are taken into account because state doesn’t exist for functional components.
Using hooks is neater to show. The new props data passed to ComponentB causes a re-rendering of ComponentB:
import React, { useState } from 'react'
import ComponentB from '...'
const ComponentA = props => {
const [data, setData] = useState(0) // data = 0
handleChangeProp = item => setData(item) // data = 1
return(
<div>
<button onClick{() => handleChangeProp(1)}
<ComponentB props={data} />
</div>
)
}
Yes, when you do a setState(newState) or when you pass in changed props the component will re render, this is why you can't mutate. The following will not work because you set state with mutated state.
export default function Parent() {
const [c, setC] = useState({ c: 0 });
console.log('in render:', c);
return (
<div>
<button
onClick={() =>
setC(state => {
state.c++;
console.log('state is:', state);
return state;
})
}
>
+
</button>
<Child c={c.c} />
</div>
);
}
That code "won't work" because pressing + will not cause a re render, you mutated state and then set state with the same object reference so React doesn't know you changed anything.
This is how React detects changes, you may think that comparing {c:0} to {c:1} is a change but because you mutated there actually is no change:
const a = {c:1};
a.c++;//you "changed" a but a still is a
To indicate a change in React you have to create a new reference:
const a = {c:1};
const b = {...a};//b is shallow copy of a
a===b;//this is false, even though both a and b have same internal values
This means you can also have unintended renders because you create an object prop that may have the same value but still is a different reference than the last time you created it.
Note that even <Child prop={1} will cause Child to render if Child is not a pure component (see links at the end).
What you want to avoid is doing <Child prop={{c:value}} because every time you pass prop it'll force Child to render and React to do a virtual DOM compare even if value didn't change. The virtual DOM compare will probably still detect that Child virtual DOM is the same as last time and won't do an actual DOM update.
The most expensive thing you can do is <Child onEvent={()=>someAction(value)}. This is because now the virtual DOM compare will fail even if value and someAction did't change. That's because you create a new function every time.
Usually you want to memoize creating props in a container, here is an example of doing this with react-redux hooks. Here is an example with stateful components passing handlers.

Is this.setState({}) guaranteed to cause a re-render

I inherited some code to maintain, with this line in it:
this.setState({}); // Force update
Is this guaranteed to cause a re-render?
setState merges the object passed as argument into the actual state in an immutable way. this.setState({}) will merge nothing to state but will actually return a new object, the shallow comparison performed by React will always assert to false and a re render will be triggered, unless explicitly cancelled with shouldComponentUpdate. So yes, in this case it is equivalent to forceUpdate and it comes with the same caveats.
You can actually test it easily:
import React, { Component } from 'react';
import { Button } from 'react-native';
class Test extends Component {
render() {
console.log('render');
return <Button onPress={() => this.setState({})} title='Test' />;
}
}
export default Test;
Every time the button is clicked, the console.log triggers.
An interesting point is that if you replace this.setState({}) by this.setState(), there is no re-render after a click.
According to the documentation it is and several other methods in order
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
please see this link for detailed
It depends. If you want to render a component, react internally checks is DOM equals with previous(this occurs if props of component is not changed at all). If dom equals with previous version, react checks shouldComponentUpdate. forceUpdate is different than this.setState({}), which always render components.

Redux mapStateToProps called multiple times

I have this very simple Component, which is connected to redux state and returns {fruit, vegetables}. Everything works fine, but let's say I have a graph inside the Component and I if receive only updated vegetable from API the graph is being recreated each time.
Here's my component:
const Products = ({ fruit, vegetable }) =>
<div className='Products'>
<div>{vegetable.map(....logic here)}</div>
<div>{Math.random()}</div> // this is to illustrate the component is rendering every time
<Graph>Here will be a chart or a graph with fruit</Graph> //but it gets re-rendered even though there is no new fruit
</div>
const mapStateToProps = (state) => {
return {
fruit: state.data.fruit,
vegetable: state.data.vegetable,
}
}
export default connect(mapStateToProps)(Products)
It seems to me that every-time, no matter which states is updated it re-renders the whole components.
Is there a way to prevent that?
When a React component gets rendered, the whole tree of components below it also gets rendered - at the exception of the components which shouldComponentUpdate hook returns false. So in your case, if the Products component gets rendered, it is normal that the Graph component also does.
You have two options here:
if your Products component does not use the fruit prop outside of the Graph component, you can connect directly your Graph component to the fruitstate, and use the pure option of the connect function to avoid re-renders when fruit does not change
you can define the shouldComponentUpdate hook in your Graph component to manually skip unnecessary renders, or use a helper library to do it for you, for example the pure helper of the recompose library
The first option is where optimizing react/redux apps / avoiding unnecessary renders generally starts: connect your components to the store at the lowest level where it makes sense. The second option is more of an escape hatch - but still often useful.
As you mention you use stateless components, you can use a higher-order component to benefit from the shouldComponentUpdate hook. To understand how this works, here's how a simple implementation of it could look like this:
function pure(BaseComponent, shouldUpdateFn) {
return class extends Component {
shouldComponentUpdate(nextProps) {
return shouldUpdateFn(this.props, nextProps);
}
render() {
return <BaseComponent { ...this.props } />;
}
}
}
This would give you a pure HOC that you could reuse over your app to avoid unnecessary renders: it works by wrapping your stateless component into a new component with the desired hook. You'd use it like so, for example:
export default pure(Graph, (props, nextProps) => props.fruit !== nextProps.fruit)
Still, i highly encourage you in having a look at recompose, which has more fine-grained implementations of this, and would avoid you to reinvent the wheel.
To prevent a component to rerender when receiving new props, you can implement shouldcomponentupdate() in Graph.
Use shouldComponentUpdate() to let React know if a component's output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.
Returning false does not prevent child components from re-rendering when their state changes.
Currently, if shouldComponentUpdate() returns false, then componentWillUpdate(), render(), and componentDidUpdate() will not be invoked. Note that 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.
If you determine a specific component is slow after profiling, you may change it to inherit from React.PureComponent which implements shouldComponentUpdate() with a shallow prop and state comparison. If you are confident you want to write it by hand, you may compare this.props with nextProps and this.state with nextState and return false to tell React the update can be skipped.
To help you implementing shouldComponentUpdate(), you can use eitherReact shallow-compare() or a custom shallow compare function
Given your current code.
React will update the whole component when state is changed.
So Graph Component will get updated.
If you don't want Graph Component to get updated you can add shouldComponentUpdate in your Graph Component and introduce checks there for re-rendering like as follows
shouldComponentUpdate: function(nextProps, nextState) {
// You can access `this.props` and `this.state` here
// and check them against nextProps and nextState respectively.
// return boolean(false) if you don't want the component to re-render.
}

Resources