I m having one child ponent which is inside a loop of parent ponent. when one of the child ponents is updating the state of parent ponent, it is re-rendering the all children since it is loop. How can i avoid the re-render for each iteration.
function Parent() {
const [selectedChild, setSelectedChild] = useState([]);
const onChangeHandle = (event, id) => {
const checked = event.target.checked;
let updatedArray = [...selectedChild];
if(checked){
if(!selectedChild.includes(id)){
updatedArray.push(id);
}
}
else{
var index = updatedArray.indexOf(id)
if (index !== -1) {
updatedArray.splice(index, 1);
}
}
setSelectedChild(updatedArray);
}
const dummy = (id) => {
return selectedChild.includes(id);
}
return (
<div>
<table>
<tbody>
{[1,2,3].map((value, index) => {
return (
<Child
key={index}
index={index}
value={value}
handle={onChangeHandle}
isSelected={dummy}
/>
)
})}
</tbody>
</table>
<div>
{selectedChild}
</div>
</div>)
}
function Child({index, value, handle, isSelected }) {
console.log('rendering')
return (
<tr>
<td>
<input
type="checkbox"
checked={isSelected(index)}
onChange={(event) => handle(event, index)}/>
</td>
<td>hello {index} {value}</td>
</tr>
)
}
export default function App() {
return (
<div className="App">
<Parent />
</div>
);
}
Current behaviour:
In above code, When i m clicking on the checkbox in one of the children ponent, it is updating the parent ponent state(selectedChild
). So the loop is executing and all children(all table rows) are re rendering.
Expected behaviour: Only that particular row have to go for re-render
Demo:
I m having one child ponent which is inside a loop of parent ponent. when one of the child ponents is updating the state of parent ponent, it is re-rendering the all children since it is loop. How can i avoid the re-render for each iteration.
function Parent() {
const [selectedChild, setSelectedChild] = useState([]);
const onChangeHandle = (event, id) => {
const checked = event.target.checked;
let updatedArray = [...selectedChild];
if(checked){
if(!selectedChild.includes(id)){
updatedArray.push(id);
}
}
else{
var index = updatedArray.indexOf(id)
if (index !== -1) {
updatedArray.splice(index, 1);
}
}
setSelectedChild(updatedArray);
}
const dummy = (id) => {
return selectedChild.includes(id);
}
return (
<div>
<table>
<tbody>
{[1,2,3].map((value, index) => {
return (
<Child
key={index}
index={index}
value={value}
handle={onChangeHandle}
isSelected={dummy}
/>
)
})}
</tbody>
</table>
<div>
{selectedChild}
</div>
</div>)
}
function Child({index, value, handle, isSelected }) {
console.log('rendering')
return (
<tr>
<td>
<input
type="checkbox"
checked={isSelected(index)}
onChange={(event) => handle(event, index)}/>
</td>
<td>hello {index} {value}</td>
</tr>
)
}
export default function App() {
return (
<div className="App">
<Parent />
</div>
);
}
Current behaviour:
In above code, When i m clicking on the checkbox in one of the children ponent, it is updating the parent ponent state(selectedChild
). So the loop is executing and all children(all table rows) are re rendering.
Expected behaviour: Only that particular row have to go for re-render
Demo: https://codesandbox.io/s/newpro-0pezc
Share Improve this question edited Jan 8, 2021 at 14:02 SuganthiRaj asked Jan 8, 2021 at 13:48 SuganthiRajSuganthiRaj 1032 silver badges7 bronze badges 2- Well for one thing, try to use unique key property values rather than array indicies. That can definitely avoid some re-renders even if it isn't your specific problem here. – Jared Smith Commented Jan 8, 2021 at 14:05
- 1 I'm not going to vote to close this as a duplicate, but does this answer your question? – Jared Smith Commented Jan 8, 2021 at 14:07
3 Answers
Reset to default 4for that you can use React.memo that will memoize your ponent if props remains the same. But given your code you need to make some extra changes:
you have to apply
useCallback
to memoize onChangeHandle function;to memoize properly onChangeHandle you need to refactor it. you can't pass
selectedChild
directly, otherwise it memoizes its value. usesetSelectedChild
passing as argument a function that takesselectedChild
instead.your Child should receive
isSelected
as boolean value instead of function. otherwise props will remain the same and Child never updates;import React, { useState, memo, useCallback } from "react"; function Parent() { const [selectedChild, setSelectedChild] = useState([]); const onChangeHandle = useCallback((event, id) => { setSelectedChild(selectedChild => { const checked = event.target.checked; let updatedArray = [...selectedChild]; if (checked) { if (!selectedChild.includes(id)) { updatedArray.push(id); } } else { var index = updatedArray.indexOf(id); if (index !== -1) { updatedArray.splice(index, 1); } } return updatedArray; }); }, []); const dummy = id => { return selectedChild.includes(id); }; const renderChildren = () => [1, 2, 3].map((value, index) => { return ( <Child key={index} index={index} value={value} handle={onChangeHandle} isSelected={dummy(index)} /> ); }); return ( <div> <table> <tbody>{renderChildren()}</tbody> </table> <div>{selectedChild}</div> </div> ); } const Child = memo(({ index, value, handle, isSelected }) => { console.log("rendering"); return ( <tr> <td> <input type="checkbox" checked={isSelected} onChange={event => handle(event, index)} /> </td> <td> hello {index} {value} </td> </tr> ); }); export default function App() { return ( <div className="App"> <Parent /> </div> ); }
https://stackblitz./edit/so-memo-children?file=src/App.js
The basic answer is use React.memo
on Child
.
const Child = memo(function Child(...) {...})
But to make memo
work, the ponent needs to receive the same props if it shouldn't get rerendered. That means using useCallback
on onChangeHandle
:
const onChangeHandle = useCallback((event, id) => {...}, [])
But since onChangeHandle
uses selectedChild
that always changes on checkbox change, you'll also need to ref it using useRef
:
const selectedChildRef = useRef();
selectedChildRef.current = selectedChild;
and use reffed version inside of onChangeHandle
.
The last thing that needs to be done is to change isSelected
prop from function to just a flag since it needs to be run on each checkbox change:
isSelected={selectedChild.includes(index)}
https://codesandbox.io/s/newpro-forked-wxvqs
You could implement shouldComponentUpdate (doc: https://reactjs/docs/react-ponent.html#shouldponentupdate) inside the definition of Child to have more control over when it rerenders. But that's only meant for cases where you have performance issues- generally you don't have to worry about it, and letting them all rerender is standard.