When developing on the client side of Web, there are many situations where we need to implement scenarios like:
- When one item is selected in an array, the information related to that item must be displayed.
In this situation, let's look at how to avoid state duplication and errors.
Code Examples
const initialPeoples = [
{ name: "John", age: 20 },
{ name: "Jenny", age: 26 },
{ name: "Peter", age: 30 },
];
function AgeCounter() {
const [peoples, setPeoples] = useState(initialPeoples);
const [selectedPeople, setSelectedPeople] = useState(null);
return (
<>
<ul>
{peoples.map((people) => (
<li
key={people.name}
onClick={() => {
setSelectedPeople(people);
}}
>
{people.name}, age {people.age}
</li>
))}
</ul>
<button
onClick={() => {
setPeoples(
peoples.map((people) => ({ ...people, age: people.age + 1 }))
);
}}
>
Happy New Year!
</button>
<div>
selected people is: {selectedPeople?.name}, age {selectedPeople?.age}
</div>
</>
);
}
The code above will work as follows:
- Three objects that contain information about people exist in
initialPeoples
. - Displays information of those people below the
<ul>
tag. - When a specific person is clicked, the object corresponding to that person is saved in
selectedPeople
.- Displays the selected
selectedPeople
information at the bottom of the screen.
- Displays the selected
- When the
Happy New Year!
button is clicked, the age of the people increases by one.- The age displayed in selectedPeople is also increased by one. At first glance, the code may seem fine, but there is an issue.
When John is selected and the Happy New Year!
button is clicked, the age displayed in selectedPeople
does not increase.
The reason for this is that the object saved in selectedPeople
and the object I created by clicking the Happy New Year!
button are different objects.
<button
onClick={() => {
setPeoples(peoples.map((people) => ({ ...people, age: people.age + 1 })));
}}
>
Happy New Year!
</button>
setState(setPeoples)
does not operate by modifying some properties of the previous object with a pointer to the previous object within the rendering lifecycle, but always forces the creation of a new object.
By using the object creation reservation keyword {}
in peoples.map((people) => ({ ...people, age: people.age + 1 }))
, a new object was saved in the new array created by map
. Therefore, the age of the new object in the new array and the object in selectedPeople
are not related at all, so clicking the Happy New Year!
button does not increase the age.
This is also an anti-pattern that unnecessarily duplicates objects.
How can we modify it?
Replace the state named selectedPeople
with a state named selectedPeopleName
, which holds the unique name of a person as its value. Then declare selectedPeople as a regular variable and use it as follows:
const initialPeoples = [
{ name: "John", age: 20 },
{ name: "Jenny", age: 26 },
{ name: "Peter", age: 30 },
];
function AgeCounter() {
const [peoples, setPeoples] = useState(initialPeoples);
const [selectedPeopleName, setSelectedPeopleName] = useState(null);
const selectedPeople = peoples.find(
(people) => people.name === selectedPeopleName
);
return (
<>
<ul>
{peoples.map((people) => (
<li
key={people.name}
onClick={() => {
setSelectedPeopleName(people.name);
}}
>
{people.name}, age {people.age}
</li>
))}
</ul>
<button
onClick={() => {
setPeoples(
peoples.map((people) => ({ ...people, age: people.age + 1 }))
);
}}
>
Happy New Year!
</button>
<div>
selected people is: {selectedPeople?.name}, age {selectedPeople?.age}
</div>
</>
);
}
Now, even if you select John and press the Happy New Year!
button, the age will increase properly.
Top comments (0)