'useCallback is not working when using with child component of same type
So I have a parent Component and a child component. And I use the child component twice in my parent component. I pass them two different state values as props and two different events as props. I have tried to memoize both the callbacks , but both the child are re-rendered even if one child callback is triggred. Why is useCallback not working.
Parent Component:
import { useState, useCallback, useEffect, useMemo } from 'react';
import './App.css'
import List from "./components/list";
import LocalList from "./components/localList";
function App() {
const itemsToBuy = [
'Baby Shoes',
'Grinder',
'Car'
]
const [buyList, updateBuyList] = useState(itemsToBuy);
const [sellList, updateSellList] = useState([
'Bed',
'Sofa'
]);
/** code to check the re-rendering of the componnet */
useEffect(() => {
console.log(`parent is being rendered`)
})
/**trying to update the state from internal method to be passed as props */
const updateBuyClick = useCallback(val => {
updateBuyList(prev => [...prev, val])
}, [buyList])
const updateSellClick = useCallback(val => {
console.log('memo of sell is called')
updateSellList(prev => [...prev, val])
}, [sellList])
return (
<>
<div className='container'>
<div>
<h1>Items To Buy</h1>
<List itemsArray={buyList} onUpdateClick={updateBuyClick} buttonText='Add Items to Buy' idx={'list One'}></List>
</div>
<div>
<h1>Items to Sell</h1>
<List itemsArray={sellList} onUpdateClick={updateSellClick} buttonText='Add Items to Sell' idx={'list Two '}></List>
</div>
{/* <div>
<h1>List that is not re-rendere</h1>
<LocalList buttonText='Add Items to LocalList' idx={'list3 '}></LocalList>
</div> */}
</div>
</>
);
}
export default App;
Child Component:
import { useState , useEffect} from "react";
import './list.css'
function List({ itemsArray = [], buttonText, onUpdateClick, idx }) {
let currentSell = '';
useEffect(() => {
console.log(`${idx} is being rendered`)
})
const updateCurrentSell = (val) => {
currentSell = val;
}
return (
<>
<ul>
{itemsArray.map((value, index) => {
return <li key={index}>{value}</li>
})}
</ul>
<div>
<input type='text' onChange={(e) => { updateCurrentSell(e.target.value) }}></input>
<button onClick={() => { onUpdateClick(currentSell) }}>{buttonText}</button>
</div>
</>
)
}
export default List;
Solution 1:[1]
There are two reasons that's not working:
You're telling
useCallbackto throw away the stored copy of your function when thebuyListorsellListchanges by including those in your dependencies array. You don't need those dependencies, because you're (correctly) using the callback version of the state setters. So you aren't usingbuyListorsellListin the callbacks. Just remove them from the arrays.const updateBuyClick = useCallback(val => { updateBuyList(prev => [...prev, val]) }, []) // ^^??? empty const updateSellClick = useCallback(val => { console.log('memo of sell is called') updateSellList(prev => [...prev, val]) }, []) // ^^??? emptyuseCallbackonly does half the necessary work: making sure the functions don't change unnecessarily. But yourListcomponent has to do the other half of the work: not re-rendering if its props don't change. With a function component, you do that withReact.memo:const List = React.memo(function List({ itemsArray = [], buttonText, onUpdateClick, idx }) { // ... });React.memomemoizes the component and reuses its last rendering if its props don't change. (You can customize that by providing a callback as its second argument, see the documentation for details.)
Between those two changes, you'll see only the appropriate instances of List re-render when things change.
Live Example:
const { useState, useCallback, useEffect, useMemo } = React;
function App() {
const itemsToBuy = [
"Baby Shoes",
"Grinder",
"Car"
];
const [buyList, updateBuyList] = useState(itemsToBuy);
const [sellList, updateSellList] = useState([
"Bed",
"Sofa"
]);
// *** Note: No need for this to be in `useEffect`
console.log(`parent is being rendered`)
const updateBuyClick = useCallback(val => {
updateBuyList(prev => [...prev, val]);
}, []);
const updateSellClick = useCallback(val => {
updateSellList(prev => [...prev, val])
}, []);
return (
<div className="container">
<div>
<h1>Items To Buy</h1>
<List itemsArray={buyList} onUpdateClick={updateBuyClick} buttonText="Add Items to Buy" idx={"list One"}></List>
</div>
<div>
<h1>Items to Sell</h1>
<List itemsArray={sellList} onUpdateClick={updateSellClick} buttonText="Add Items to Sell" idx={"list Two "}></List>
</div>
</div>
);
}
const List = React.memo(function List({ itemsArray = [], buttonText, onUpdateClick, idx }) {
// *** `currentSell` stuff should be in state, not a local variable
const [currentSell, setCurrentSell] = useState("");
console.log(`${idx} is being rendered`);
return ( // <>...</> is fine, I had to change it because the
// version of Babel Stack Snippets use is out of date
<React.Fragment>
<ul>
{itemsArray.map((value, index) => {
return <li key={index}>{value}</li>
})}
</ul>
<div>
<input type="text" onChange={(e) => { setCurrentSell(e.target.value); }}></input>
<button onClick={() => { onUpdateClick(currentSell); }}>{buttonText}</button>
</div>
</React.Fragment>
);
});
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
