Reputation: 103
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;
Upvotes: 2
Views: 2796
Reputation: 1075587
There are two reasons that's not working:
You're telling useCallback
to throw away the stored copy of your function when the buyList
or sellList
changes 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 using buyList
or sellList
in 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])
}, [])
// ^^−−− empty
useCallback
only does half the necessary work: making sure the functions don't change unnecessarily. But your List
component 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 with React.memo
:
const List = React.memo(function List({ itemsArray = [], buttonText, onUpdateClick, idx }) {
// ...
});
React.memo
memoizes 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>
Upvotes: 1