Reputation: 1669
I am trying to optimize my React list rendering using the React memo feature of manual props comparison. I have generated a list of simple "toggle" buttons:
import React, { useState } from "react";
import "./styles.css";
import { Toggle } from "./Toggle";
export default function App() {
const [list, setList] = useState({ a: true, b: true, c: true });
const handleClick = x => {
console.log(list);
const currentValue = list[x];
setList({ ...list, [x]: !currentValue });
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{Object.keys(list).map(x => (
<Toggle key={x} isChecked={list[x]} name={x} onChange={handleClick} />
))}
</div>
);
}
This is the "toggle" button:
import React from "react";
const areEqual = (prevProps, nextProps) => {
return prevProps.isChecked === nextProps.isChecked;
};
const ToggleComponent = ({ isChecked, name, onChange }) => {
return (
<>
<h1>{isChecked ? "This is true" : "This is false"}</h1>
<button onClick={() => onChange(name)}>{name}</button>
</>
);
};
export const Toggle = React.memo(ToggleComponent, areEqual);
My issue is that the list object actually doesn't store the expected value. Every time I click on the buttons I get the same, default one { a: true, b: true, c: true }
(it is visible in the console.log
of handleClick
), but if I delete the areEqual
function, everything works properly again and the list object is updated as it should be.
EDIT:
I saw that if I change the whole thing into an array and wrap every button into an object, the memo feature works as intended.
Code Sandbox example with array
Upvotes: 1
Views: 177
Reputation: 2958
It is because the handleClick
function is created and passed once to a Toggle
Component.
And handleClick's
closure contains the old list
value, so whenever the old value change it doesn't get updated.
The easiest fix is to benefit from the second signature of the state updater: a function that accepts in parameter the old state value.
So whenever it is called, react will pass the old state value to it.
const handleClick = x => {
setList(old => ({ ...old, [x]: !old[x] }));
};
You also need to memoize
the handleClick function, because it is recreated at each render
of the component holding the state:
const handleClick = React.useCallback(x => {
setList(old => ({ ...old, [x]: !old[x] }));
}, [setList]);
Here is working codesandbox
Upvotes: 1