I have an object containing an array that I use to generate a list of inputs. In this example, the initial value is aaa bbb ccc. Here is the initial value of the object (full code later).
{ name: "tttt", versions: [{ name: "aaa" }, { name: "bbb" }, { name: "ccc" }
here's a screenshot after I typed "cf" after bbb. As you can see, only the c appears in the UI, the "f" does not. (only showing the .versions array, trying to keep it as concise as possible)
I update the value of the object using useState. I also monitor it using useEffect, the "f" char does appear there, see screenshot of the console.
Here's the full code
import React, { useEffect, useState } from "react";
import "./App.css";
function Test() {
const [product, product_set] = useState({});
const [currentVersionIndex, currentVersionIndex_set] = useState(0);
useEffect(() => {
console.log("this should only run once ===================");
product_set({ name: "tttt", versions: [{ name: "aaa" }, { name: "bbb" }, { name: "ccc" }] });
}, []);
useEffect(() => {
console.log("Step test uE[] product=", product);
}, [product]);
useEffect(() => {
console.log("Step test uE[] currentVersionIndex=", currentVersionIndex);
}, [currentVersionIndex]);
const handleEditVersion = async (ev) => {
const newValue = ev.target.value;
currentVersionIndex_set(parseInt(ev.target.id));
const idx = parseInt(ev.target.id);
// I tried both with idx and with currentVersionIndex
let v = product.versions;
v[idx].name = ev.target.value;
const selObj = product;
selObj.versions = v;
product_set(selObj);
// versionsList_set(v)
};
return (
<div>
<ul>
{product &&
product.versions &&
product.versions.map((item, index) => (
<li>
<input type="text"
value={item.name}
key={`AAAAA_${index}`}
id={index}
onChange={handleEditVersion}
/>
</li>
))}
</ul>
</div>
);
}
export default Test;
Problem:
Line 4 and Line 7 in handleEditVersion
function actually mutates state because the v
and selObj
variable is a reference to state. React state should be treated as immutable.
Solution:
Spread operator creates a separate copy of the object that’s stored in state. Now it is safe to mutate the object on line 5 and line 8 — it’s a completely separate object from the object in state.
const handleEditVersion = (ev) => {
currentVersionIndex_set(parseInt(ev.target.id));
const idx = parseInt(ev.target.id);
// Wrong: let v = product.versions;
let v = [...product.versions]; // You have to copy the state data by using spread operator
v[idx].name = ev.target.value;
// Wrong: const selObj = product;
const selObj = { ...product }; // You have to copy the state data by using spread operator
selObj.versions = v;
product_set(selObj)
}