Prevent re-rendering when child components list expands - reactjs

In the following example, every click of the button should only render the next Number component, but instead it re-renders the whole list again. I would like to know the best practice to prevent the previous one from re-rendering. Thank you.
const Number = (props) =>{return (<div> {props.number} </div>)}
const NumberListUpTo = () =>{
const [listLength, setListLength] = useState(1);
return(
<>
<button onClick = {() => setListLength(listLength + 1)}>add one more</button>
{
Array.from({length: listLength}, (v, i) => i).map(
(i) => (<Number number = {i} />))
}
</>)
}

Use React.memo to memoize your components.
You should always provide a key for React components when you are rendering an array of elements.
Edit:
As React documentation says:
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.
Do not try to prevent renders or memoize every component. Try to optimize your code whenever you there is a performance issue. Optimization in React, using memo, useMemo, useCallback, etc) comes at a price; they take resources and may even slow down your app.

Related

is useCallback a performance boost for the react app?

could someone explain to me if the use of useMemo and useCallback is expensive or cheap? I've checked the React's source code and I think they are cheap to use, but I've also read some people saying they are not.
In a Next.js context using useCallback:
const MyComp = () => {
const router = useRouter():
const handleClick = useCallback(() => router.push('/some-path'), [router]);
return <button onClick={handleClick} />
}
vs plain arrow function here:
const MyComp = () => {
const router = useRouter():
return <button onClick={() => router.push('/some-path')} />
}
Am I saving re-renders with useCallback?
The cost of memoize and comprare the dependencies array [router], is more expensive?
Additional info: Checking the React's code I saw that they compare the deps array items using Object.is instead of === or ==.
Someone knows why?
Also checking this component render:
import {useCallback, useState} from "react";
let i = 0
const CallbackDemo = () => {
const [value, setValue] = useState('');
console.log('render', i++);
const handleClick = useCallback(() => setValue(Math.random()), []);
return (
<div>
<span>{value}</span>
<button onClick={handleClick}>New set</button>
</div>
);
}
export default CallbackDemo;
I saw the same count of renders using or not using useCallback
Am I saving re-renders with useCallback?
Not in that specific case, no. The useCallback code does let you save roughly the cost of an assignment statement because it doesn't have to update the click handler on the button element, but at the cost of a function call and going through the array of dependencies looking for differences. So in that specific case, it's probably not worth doing.
If you had a more complex child component, and that component was optimized not to re-render when called with the same props (for instance, via React.memo or React.PureComponent or similar), or you were passing the function to a bunch of child components, then you might get some performance improvements by not making them re-render.
Consider this example, where simpleClickHandler is always recreated but memoizedClickHandler is reused¹ via useCallback:
const { useState, useCallback } = React;
const Parent = () => {
const [counter, setCounter] = useState(0);
console.log("Parent called");
const simpleClickHandler = () => {
console.log("Click occurred");
setCounter(c => c + 1);
};
const memoizedClickHandler = useCallback(() => {
console.log("Click occurred");
setCounter(c => c + 1);
}, []);
return (
<div>
Count: {counter}
<Child onClick={simpleClickHandler} handlerName="simpleClickHandler">simpleClickHandler</Child>
<Child onClick={memoizedClickHandler} handlerName="memoizedClickHandler">memoizedClickHandler</Child>
</div>
);
};
const Child = React.memo(({handlerName, onClick, children}) => {
console.log(`Child using ${handlerName} called`);
return (
<div onClick={onClick}>{children}</div>
);
});
ReactDOM.render(<Parent/>, document.getElementById("root"));
<div id="root"><?div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
Note that when you click, only the child being passed simpleClickHandler re-renders, not the one being passed memoizedClickHandler, because memoizedClickHandler's value is stable but simpleClickHandler's value changes every time.
Using useCallback requires more work in the parent (checking to see if the previous function can be reused), but may help save work in child components.
It's important to note that the stability useCallback (and useMemo) give you are only appropriate for performance reasons, not correctness. React can throw away the previous copy of a handler and new a new one if it wants to, even if the deps haven't changed. If you need a correctness guarantee (the result definitely 100% will not change unless the deps change), you have use a ref. But that's a very rare use case.
Additional info: Checking the React's code I saw that they compare the deps array items using Object.is instead of === or ==.
Someone knows why?
Primarily because NaN === NaN is false, because all comparisons with NaN are false. So if they used ===, any deps array containing NaN would always be considered different from the previous one. But using Object.is avoids that problem, because Object.is(NaN, NaN) is true.
¹ Note that "reused" here means: a new handler is created every time, just like simpleClickHandler, but useCallback may return the previous handler you've given it if the deps haven't changed, so the new one is thrown away. (JavaScript engines are really quick at allocating and reclaiming short-lived objects.)
useCallback like useMemo indeed improve performance
but!!! you don't need to use it to everything because it will make your website slower.
this is better for the heavy lifting components like for charts and stuff like that.
that consume a lot of resource and take a lot of time to process and this will make this specific component load much more faster and not stuck everytime you do a change.
you can see deep compression like this:
react useEffect comparing objects
this link will show you for instance how to do a deep compression for use effect

Why doesn't memoization of a React functional component call with useMemo() hook work?

Okay, I think I might as well add here that I know that code doesn't look too good, but I thought it would do the job, so I would be glad if you could explain why not only it would not be a good practice if it worked, but why it doesn't work. I'd be glad to hear your solutions to the problem too!
I'm having a hard time understanding why the chunk of code below doesn't seem to work as intended by which I mean memoizing <First /> and <Second /> functional components' return values and not calling their functions on every <App /> render. I thought that since <SomeComponent /> expression returns an object, it would be possible to simply memoize it and go with. Doesn't seem to work and I can't wrap my head around of as to why.
On a side note, I would also be thankful if you could explain why rendering <App /> component causes the renders.current value increment by two while only calling the console.log once.
Thanks a lot for your help!
import "./styles.css";
import React from "react";
const First = () => {
const renders = React.useRef(0);
renders.current += 1;
console.log('<First /> renders: ', renders.current);
return <h1>First</h1>;
}
const Second = () => {
const renders = React.useRef(0);
renders.current += 1;
console.log('<Second /> renders: ', renders.current);
return <h1>Second</h1>;
}
const App = () => {
const [isSwapped, setIsSwapped] = React.useState(false);
const [, dummyState] = React.useState(false);
const first = React.useMemo(() => <First />, []);
const second = React.useMemo(() => <Second />, []);
const renders = React.useRef(0);
renders.current += 1;
console.log('<App /> renders: ', renders.current);
return (
<div className="App">
<button onClick={() => setIsSwapped((isSwapped) => !isSwapped)}>Swap</button>
<button onClick={() => dummyState((state) => !state)}>Re-render</button>
{isSwapped ? (
<>
{first}
{second}
</>
) : (
<>
{second}
{first}
</>
)}
</div>
);
}
Edit: Thanks for replies, guys, this is the version that does what was intended: https://codesandbox.io/s/why-doesnt-memoization-of-a-react-functional-component-call-with-usememo-hook-forked-gvnn0?file=/src/App.js
For the second part of your question relating to why the value of render.current might be updating twice rather than once it might be because you are using React.StrictMode in your index.jsx file. Removing StrictMode did seem to fix the issue in this example. Also check out this link for more info.
As for the first issue, it seems that on clicking the re-render button neithr of the child components (First or Second) re-render. However, when we swap them not only do they re-render but the components are also unmounted and then re-mounted. You can see this behaviour in the above example, as well. Providing unique keys for both components seems to fix this issue. I'm also a beginner at React and not entirely sure of what's happening behind the scenes, however this is what I have gathered so far based on the documentation: When react finds that a child (say the first child) has a different type compared to what the type was on the previous render (in our case the type switches between First and Second) it unmounts the child node and all of its children, then re-mounts them, then continues with the rendering process. This is supported by the console logs we can see int the above example. By including unqiue keys we let react know that despite components First and Second being in different locations they are the same component after all. So, react doesn't bump into a scenario where a child has swapped types.
I tried your code with the components' return values memoized via the useMemo hook and then wrapped each with React.memo and the result appeared to be the same for me.
Using the memo Higher Order Component to decorate a component will memoize the rendered result.
If your component renders the same result given the same props, you
can wrap it in a call to React.memo for a performance boost in some
cases by memoizing the result. This means that React will skip
rendering the component, and reuse the last rendered result.
const First = React.memo(() => {
const renders = React.useRef(0);
React.useEffect(() => {
renders.current += 1;
console.log("<First /> renders: ", renders.current);
});
return <h1>First</h1>;
});
const Second = React.memo(() => {
const renders = React.useRef(0);
React.useEffect(() => {
renders.current += 1;
console.log("<Second /> renders: ", renders.current);
});
return <h1>Second</h1>;
});
You'll see I've also addressed the unintentional side-effects (console log and ref mutation) in the following question:
On a side note, it would also be cool if you could help me with
understanding why parent component seems to be adding +2 to
renders.current on every render even though if I put a
console.log('render') in the component, it will only show up once.
This is because there are two phases of the component lifecycle, the "render phase" and the "commit phase".
Notice that the "render" method occurs during the "render phase" and recall that the entire component body of functional components is the "render method". This means that React can and will possible render the component several times in order to compute a diff for what needs to be rendered to the DOM during the "commit phase". The "commit phase" is what is traditionally considered rendering a component (to the DOM). Note also that this phase is where side-effect can be run, i.e. useEffect.
Place the logs and ref update in an useEffect hook to run them once per render cycle.
function App() {
const [isSwapped, setIsSwapped] = React.useState(false);
const [, dummyState] = React.useState(false);
const renders = React.useRef(0);
React.useEffect(() => {
renders.current += 1;
console.log("<App /> renders: ", renders.current);
});
return (
<div className="App">
<button onClick={() => setIsSwapped((isSwapped) => !isSwapped)}>
Swap
</button>
<button onClick={() => dummyState((state) => !state)}>Re-render</button>
{isSwapped ? (
<>
<First />
<Second />
</>
) : (
<>
<Second />
<First />
</>
)}
</div>
);
}
Demo

Is this incorrect use of useCallback and useMemo?

When should we worry about functional components redefining variables and functions??
I think this first case is clearly unnecessary, functions is not expensive to create(and the react dev tools profile test didn't detect performace change with useCallback)
import React, { useState, useMemo, useCallback } from "react";
export const App = () => {
const [counter, setCounter] = useState(1);
const showCounter = useCallback(() => {
console.log(counter);
}, [counter]);
return (
<>
<button onClick={showCounter}>Show</button>
<button onClick={() => setCounter(counter + 1)}>Add</button>
</>
);
};
In the other hand, in this other exemple, with react dev tools we can detect that useMemo cuts in half the time of rendering:
import React, { useState, useMemo, useCallback } from "react";
export const App = () => {
const [counter, setCounter] = useState(1);
const [list, setList] = useState<number[]>([]);
function setListItem(item: number) {
setList([...list, item]);
}
//should we useMemo here?
const domList = useMemo(
() =>
list.map((value: number, index: number) => (
<li key={index}> {value} </li>
)),
[list]
);
return (
<>
<p>{counter}</p>
<button onClick={() => setCounter(counter - 1)}>Subtract</button>
<button onClick={() => setCounter(counter + 1)}>Add</button>
<button onClick={() => setListItem(counter)}>Push to array</button>
<h1>List</h1>
<ul>{domList}</ul>
</>
);
};
But still less than a millisecond of gain and array maps and JSX array are not something really expensive when we talk about front-end either. However, leaving an array map method run in each render is very uncomfortable to see. So, what of those things should we do? (Let's forget about when we have useEffect that depends of some variables that make the useMemo necessary)
useCallback and useMemo in all functions and variables that don't need to be redefined in each render.
useCallback and useMemo only in things that we can clearly see a performance gain(even if most time would not count as an improvement to UX). So we would need useMemo in this second example.
useCallback and useMemo only when not using it in a specific case we would get a great UX decline. So only really expensive functions would useCallback
The performance you gain with each of them is different. Yes, for useMemo if the calculation you are memoizing is expensive, it offers a clear immediate benefit. However, for useCallback and useMemo, the long-term benefits refer to avoiding unnecessary renders in child components, not the recalculation cost.
Remember, if any of the props in a component change, they may trigger a render call, and the higher in the component tree this is the case, the more it will affect performance. That is the reason we normally use those hooks.
In general, components have several state elements, and with memo hooks, we can establish dependencies to avoid computational redundancy. For instance, in your synthetic case, you have two state elements: counter, and list. Each time counter changes, you prevented domList to be recalculated by making it dependent only on list. If this was a leaf node, sure no performance gain, however, this is the root of your component tree, and without those hooks, performance will not scale as you add more state elements and you will have to refactor your code to address performance. Sure, <ul>{domList}</ul> can be costly if the list has more than 1000 elements, but <ul><ExpensiveCutie domList={domList} /></ul> can take you weeks to notice, debug, and refactor.
In short, I recommend using memo hooks in container components and not in leaf components. Yet, what if your leaf component becomes a container as you are implementing it?
Before you go, do you even need to use memo()? Read more here.

Reducing React table rerendering

I have a table with two columns, each of which are editable. The table also includes the ability to add new entries. The problem I'm running into is that whenever a single row is edited, each row is re-rendered. For larger tables this causes noticeable keystroke delays when editing a single row. Any ideas as to how this can be prevented?
I haven't been able to replicate the performance problems in a "small" example, but https://codesandbox.io/s/zen-williams-r8pii?file=/src/App.js is the basic functionality. I've tried dropping React.memo in a few places to no avail (I'm a newbie at this stuff)
Thanks for any help!
There were some things that were making the row rerender:
First, the row component needed to be wrapped in React.memo
const ThingRow = React.memo(({ thing, onUpdate }) => {
...
})
Then the onUpdate needed to be memoized as well with React.useCallback, and to reduce the dependencies in useCallback to only setThings(see note), you can use the callback version of setThings and .map to update the item
const ListOfThings = ({ things, setThings }) => {
const onUpdate = React.useCallback(
(thing) => {
setThings((prevThings) =>
prevThings.map((t) => (t.id === thing.id ? thing : t))
);
},
[setThings]
);
return (
<table>
<tbody>
{things.map((thing) => (
<ThingRow key={thing.id} thing={thing} onUpdate={onUpdate} />
))}
</tbody>
</table>
);
};
When having this kind of issue you need to look for the way to memoize props you are passing down, especially callbacks, and reduce dependencies in hooks
this is the codesandbox fork https://codesandbox.io/s/table-row-memoization-xs1d2?file=/src/App.js
note: according react docs
React guarantees that setState function identity is stable and won’t change on re-renders
There are a couple of optimizations you can try.
First of all, if you are dealing with a lot of data, the core problem could be that you are rendering too many things to the DOM.
If this is the case, I would start by adding virtualization or pagination to the table.
You could easily add virtualization with a library, e.g. using FixedSizeList from react-window:
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
const Example = () => (
<List
height={150}
itemCount={1000}
itemSize={35}
width={300}
>
{Row}
</List>
);
This should already cut down on any performance problems, but if you only want to optimize rerenders:
The default behavior for react is to render the current component where a useState setter was called, and all child components, even without any props changing.
so you should wrap both ListOfThings and ThingRow in memo to opt out of this behavior:
const ListOfThings = memo({ things, setThings }) => {
...
});
const ThingRow = memo(({ thing, onUpdate }) => {
...
});
If you don't do this, changing the unrelated title input will lead to rerendering everything.
Lastly, you are passing an inline event handler (onUpdate) to ThingRow.
This will create a new function object for each rendering of ListOfThings, which would bypass the memo optimization we did earlier.
To optimize this, you could create a persistent reference to the function with useCallback:
onUpdate={useCallback(
thing => setThings(prev => prev.map(t => t.id === thing.id ? thing : t)),
[setThings])}
Note that it's considered best to use the function update style in useState whenever you are relying on a previous value for the update.
Small disclaimer: If you are having performance problems, I would go only with the virtualization / pagination solution, and not try at all to optimize for rerenders. React's default rendering behavior is very fast, and changing it could potentially introduce hard to find bugs related to things not updating.

React Performance issues in passing callback as props in functional components

React doc
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// This syntax ensures `this` is bound within handleClick
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
React doc :-
The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the class fields syntax, to avoid this sort of performance problem.
This is fine for class based component, what about functional component.
Can we use like below and experience performance issues like it stated because different callback is created each renders?
It says it works most of the cases, so I am not sure if we should leave it as it is or use React hook useCallBack for all functional components with callbacks.
I have got 3 diff types
const LoggingButtion = (props)=>{
const [ loggedStatus, setLogStatus] = useState(false);
const handleClick =()=> {
console.log('Button Clicked');
setLogStatus(true)
}
return (
<button onClick={() => { console.log('button clicked'); setLogStatus(false)} > // Type1
<button onClick={() => handleClick()}> // type 2
<button onClick={handleClick}> // type 3
Click me
</button>
);
}
Can we use like below and experience performance issues like it stated because different callback is created each renders? It says it works most of the cases, so I am not sure if we should leave it as it is or use React hook useCallBack for all functional components with callbacks.
It only matters if the app is large enough that it's causing performance issues, which isn't likely. Avoiding useCallback makes the code easier to read and will work just fine in 97% of cases. But if you do find a situation where child components are re-rendering too often, and you have enough of those components that the performance impact is visible, go ahead and use useCallback:
const handleClick = useCallback(() => () => {
console.log('Button Clicked');
setLogStatus(true)
}, []);
But, note that you don't have child components here, only <button>s. While a different event listener has to be attached to each element each render, that's not likely to be a problem either.
Feel free to do what you're currently doing, without useCallback, and only change to useCallback if re-rendering proves to be a problem.

Resources