setState mutating initialized object

Hey all swiss cheese question here. I have two states, a current state, and a revertable state which are both initiated by the same object:
const mockData = {
firstName: "Barbara",
meta: {
foo: "bar",
},
};
const mockData = {
firstName: "Barbara",
meta: {
foo: "bar",
},
};
export default function Form({ mockData }) {

const [person, setPerson] = useState(mockData);
const [prevState] = useState(mockData);

function handleNameChange(e) {
const updatedPerson = { ...person };
updatedPerson.firstName = e.target.value;
setPerson(updatedPerson);
}

function handleTest(e) {
const updatedPerson = { ...person };
updatedPerson.meta.foo = e.target.value;
setPerson(updatedPerson);
}

return (
<>
<label>
First name:
<input
name="firstName"
value={person.firstName}
onChange={handleNameChange}
/>
</label>
<label>
test:
<input
name="groupName"
value={person.meta.foo}
onChange={handleTest}
/>
</label>
<p>Current:</p>
<pre> {JSON.stringify(person, null, 2)}</pre>
<p>Previous:</p>
<pre> {JSON.stringify(prevState, null, 2)}</pre>
</>
);
}
export default function Form({ mockData }) {

const [person, setPerson] = useState(mockData);
const [prevState] = useState(mockData);

function handleNameChange(e) {
const updatedPerson = { ...person };
updatedPerson.firstName = e.target.value;
setPerson(updatedPerson);
}

function handleTest(e) {
const updatedPerson = { ...person };
updatedPerson.meta.foo = e.target.value;
setPerson(updatedPerson);
}

return (
<>
<label>
First name:
<input
name="firstName"
value={person.firstName}
onChange={handleNameChange}
/>
</label>
<label>
test:
<input
name="groupName"
value={person.meta.foo}
onChange={handleTest}
/>
</label>
<p>Current:</p>
<pre> {JSON.stringify(person, null, 2)}</pre>
<p>Previous:</p>
<pre> {JSON.stringify(prevState, null, 2)}</pre>
</>
);
}
When using handleNameChange, both states behave as expected; person updates, and prevState stays the same. But using handleGroupNameChange both states update together! Something to do with the nested object getting referenced when the initial state is being set. I'm surprised the "setState" affects the initial state object, I assumed the object would be cloned. What is the best approach around this? Any references I can read will be welcome. Repo here: https://codesandbox.io/p/sandbox/zsc923 Thanks!
1 Reply
zebwd
zebwd3d ago
Hi, by handleGroupNameChange I assume you mean the handleTest function in your code snippet? At first glance I can see you're doing
const updatedPerson = { ...person }
const updatedPerson = { ...person }
This creates a shallow copy of the person state object, by shallow copy I mean it won't copy the nested objects, in this case the meta object So when you do
updatedPerson.meta.foo = e.target.value
updatedPerson.meta.foo = e.target.value
the meta key is still referencing the meta object from the state, and when you mutate the state that way React breaks. You could do this to create a deep copy, then you can mutate it freely:
const updatedPerson = { ...person, meta: {...person.meta} };
const updatedPerson = { ...person, meta: {...person.meta} };
But I would recommend referencing the previous state like this:
setPerson(prevPerson => { ...prevPerson, meta: {...prevPerson.meta, foo: e.target.value }});
setPerson(prevPerson => { ...prevPerson, meta: {...prevPerson.meta, foo: e.target.value }});
you usually want to use spreads to avoid mutating stuff in React One final suggestion, if you want to keep references to certain data inside a component, you can also use Refs, that way you avoid weird syntax: Instead of:
const [prevState] = useState(mockData);
const [prevState] = useState(mockData);
You can use:
const prevPersonRef = useRef(mockData);
const prevPersonRef = useRef(mockData);
I don't even remember where I learned this from other than experience but I think this article explains the shallow copy concept simply https://dev.to/aditi05/shallow-copy-and-deep-copy-10hh
DEV Community
Shallow Copy and Deep Copy
Table of Contents Introduction Shallow Copy Deep Copy Summary References ...
Want results from more Discord servers?
Add your server