I'm rendering a series of frames from a video, each in their own component. The frames are stored as a state within a parent component called Timeline.
Since I need to manipulate elements of the array of frames from the children components, I pass in callback functions which have the index i with a closure. To avoid changes in individual frames affecting all frames, I've used the memo higher-order component around each Frame.
Nevertheless, I've found that any time I select a frame, all of the frames are re-rendered. Using the whyDidyouUpdate hook (https://usehooks.com/useWhyDidYouUpdate/), I've pinpointed it down to my callback changing on each render.
I tried doing onClick={useCallback(() => selectFrame(i), [])}, but instead, the frame simply does not change colors, indicating to me that this has somehow impeded the callback from properly functioning.
let Frame = memo(({frame, ...}) => (
<img style={{border: frame.selected ? "green" : "red";}} />
);
let Timeline = () => {
let [frames, setFrames] = useState([
{ id: "00000", selected: True, ...more image information },
{ id: "00001", selected: True, ...more image information },
{ id: "00002", selected: True, ...more image information },
]);
let selectFrame = (i) => {
let newFrames = [...frames];
newFrames[i].mask = base64;
setFrames(newFrames);
}
return (
<div>
{frames.map((f, i) => (
<Frame
frame={f}
key={f.id}
onClick={() => selectFrame(i)}
/>
)}
</div>
));
}
Any suggestions for the correct way to handle this? Thanks
EDIT: Drew's solution below pointed me in the correct direction. The proper syntax is:
const selectFrame = (i) => useCallback(() =>
setFrames(frames.map((f, j) => (
(i === j) ? {...f, selected: !f.selected} : f
))), []);
You need to have a different memoized callback for each function.