Reputation: 3451
I've created a simple example of how useCallback
is not allowing me to preserve state changes. When I remove the useCallback, the counters that I store in state update as expected, but adding useCallback (which I was hoping would keep rerenders of all speaker items to not re-render) keeps resetting my state back to the original (0,0,0).
The problem code is here in codesandbox:
https://codesandbox.io/s/flamboyant-shaw-2wtqj?file=/pages/index.js
and here is the actual simple one file example
import React, { useState, memo, useCallback } from 'react';
const Speaker = memo(({ speaker, speakerClick }) => {
console.log(speaker.id)
return (
<div>
<span
onClick={() => {
speakerClick(speaker.id);
}}
src={`speakerimages/Speaker-${speaker.id}.jpg`}
width={100}
>{speaker.id} {speaker.name}</span>
<span className="fa fa-star "> {speaker.clickCount}</span>
</div>
);
});
function SpeakerList({ speakers, setSpeakers }) {
return (
<div>
{speakers.map((speaker) => {
return (
<Speaker
speaker={speaker}
speakerClick={useCallback((id) => {
const speakersNew = speakers.map((speaker) => {
return speaker.id === id
? { ...speaker, clickCount: speaker.clickCount + 1 }
: speaker;
});
setSpeakers(speakersNew);
},[])}
key={speaker.id}
/>
);
})}
</div>
);
}
//
const App = () => {
const speakersArray = [
{ id: 1124, name: 'aaa', clickCount: 0 },
{ id: 1530, name: 'bbb', clickCount: 0 },
{ id: 10803, name: 'ccc', clickCount: 0 },
];
const [speakers, setSpeakers] = useState(speakersArray);
return (
<div>
<h1>Speaker List</h1>
<SpeakerList speakers={speakers} setSpeakers={setSpeakers}></SpeakerList>
</div>
);
};
export default App;
Upvotes: 0
Views: 729
Reputation: 10382
first, you can only use a hook at component body, you can't wrap it at speakerClick
props function declaration. second, useCallback
will keep the original speakers
object reference, which will be a stale value. To solve this, you can use setSpeakers
passing a callback instead, where your function will be called with the current speakers
state:
function SpeakerList({ speakers, setSpeakers }) {
const speakerClick = useCallback(
(id) => {
// passing a callback avoid using a stale object reference
setSpeakers((speakers) => {
return speakers.map((speaker) => {
return speaker.id === id
? { ...speaker, clickCount: speaker.clickCount + 1 }
: speaker;
});
});
},
[setSpeakers] // you can add setSpeakers as dependency since it'll remain the same
);
return (
<div>
{speakers.map((speaker) => {
return (
<Speaker
speaker={speaker}
speakerClick={speakerClick}
key={speaker.id}
/>
);
})}
</div>
);
}
Upvotes: 1