I am making changes to a set of data and the data is held in a parent ponents state I pass that data to a 2nd ponent but when I make changes to the data in the 2nd ponent it is changing the data in the first. Ive tried setting a variable to the value of the state in an effort to make a copy of it / also creating a new state in the 2nd ponent that is set from the initial data from the first. But For some reason I am still changing the initial data. Can anyone explain whats going on here?
Parent Component:
const [data, setData] = useState([]);
const [editing, setEditing] = useState(null);
{!!editing && (
<EditInsurance state={editing} data={data} setEditing={setEditing} />
)}
2nd Component:
const [update, setUpdate] = useState([]);
const [List, setList] = useState([]);
//sets the data onLoad
useEffect(() => {
setList(data);
}, []);
//edits the data
const handleClick = (item) => {
let list = List;
list.forEach((el) => {
if (el.payerName === item.payerName) {
el.state.push(state.value);
if (!el.isActive) {
el.isActive = true;
}
}
});
//update is an array containing only edited objects.
setUpdate([...update, item]);
};
When handleClick
is fired and I console the data
value in the parent ponent it has been updated. And I dont want to change that data until the user clicks save. How can I have a copy of the data exclusive to the 2nd ponent that doesn't manipulate the data in the Parent?
I am making changes to a set of data and the data is held in a parent ponents state I pass that data to a 2nd ponent but when I make changes to the data in the 2nd ponent it is changing the data in the first. Ive tried setting a variable to the value of the state in an effort to make a copy of it / also creating a new state in the 2nd ponent that is set from the initial data from the first. But For some reason I am still changing the initial data. Can anyone explain whats going on here?
Parent Component:
const [data, setData] = useState([]);
const [editing, setEditing] = useState(null);
{!!editing && (
<EditInsurance state={editing} data={data} setEditing={setEditing} />
)}
2nd Component:
const [update, setUpdate] = useState([]);
const [List, setList] = useState([]);
//sets the data onLoad
useEffect(() => {
setList(data);
}, []);
//edits the data
const handleClick = (item) => {
let list = List;
list.forEach((el) => {
if (el.payerName === item.payerName) {
el.state.push(state.value);
if (!el.isActive) {
el.isActive = true;
}
}
});
//update is an array containing only edited objects.
setUpdate([...update, item]);
};
When handleClick
is fired and I console the data
value in the parent ponent it has been updated. And I dont want to change that data until the user clicks save. How can I have a copy of the data exclusive to the 2nd ponent that doesn't manipulate the data in the Parent?
- 2 Objects in JavaScript are passed by a reference. You should clone the list instead of pushing to it. – Konrad Commented Apr 14, 2022 at 22:16
- You have a function to update the list: setUpdate, so use it instead of directly modifying the value 'List' – hungdoansy Commented Apr 15, 2022 at 11:48
7 Answers
Reset to default 4 +25Issue
The issue here is that the code is mutating the state objects.
const [update, setUpdate] = useState([]);
const [List, setList] = useState([]);
// sets the data onLoad
useEffect(() => {
setList(data); // <-- (1) parent state passed as prop
}, []);
// edits the data
const handleClick = (item) => {
let list = List; // <--(2) reference to state in parent
list.forEach((el) => {
if (el.payerName === item.payerName) {
el.state.push(state.value); // <-- (3) mutation of parent state!
if (!el.isActive) {
el.isActive = true; // <-- (3) mutation of parent state!
}
}
});
// update is an array containing only edited objects.
setUpdate([...update, item]);
};
Solution
Use/apply the immutable update patten. Any state that is being updated should be shallow copied into to new array/object reference. The spread syntax is a shallow copy by reference, this is why creating new array/object references is necessary to keep from mutating the original.
I'll assume that this child ponent just wants/needs to maintain its own copy of the data
prop.
const [list, setList] = useState(data); // <-- (1) parent state passed as prop
// edits the data
const handleClick = (item) => {
setList(list => list.map(el => { // <-- (2) array.map shallow copy
if (el.payerName === item.payerName) {
return {
...el, // <-- (3) shallow copy array element
state: [...el.state, state.value], // <-- (4) shallow copy array
isActive: !el.isActive, // <-- new property
};
}
return el; // <-- not updating, just return current element
}));
};
You can try to copy the list using a spread operator. On the child ponent, you can do something like:
function EditInsurance(props) {
const [parentData, setParentData] = useState([...props.data]);
...
}
And then use the parentData on your child ponent, you don't even need the useEffect.
You'll need deep cloning to preserve individual objects in parent
const [update, setUpdate] = useState([]);
const [list, setList] = useState([]);
useEffect(() => {
const cloned = data.map(item => {...item}) // clone list and items
setList(cloned);
}, []);
//edits the data
const handleClick = (item) => {
list.forEach((el) => {
if (el.payerName === item.payerName) {
el.state.push(state.value);
if (!el.isActive) {
el.isActive = true;
}
}
});
setUpdate([...update, item]);
}
Your handleClick()
can probably be shortened to
const handleClick = (item) => {
const payer = list.find(el => el.payerName === item.payerName);
payer.state.push(state.value);
payer.isActive = true;
setUpdate([...update, item]);
}
I'm not very sure but you can try something like -
const [List, setList] = useState([]);
const [newList, setNewList] = useState([]);
and along with -
useEffect(() => {
setList(data);
}, []);
use -
useEffect(() => {
setNewList([...List])
}, [List]);
This will make a cloned copy of the List props that you received, and now if you change this (push or setState), your parent ponent will not reflect the changes.
Now in your handleClick function, -
const handleClick = (item) => {
let MyList = [...newList]
# this is brcause you are using map. So first push the data in MyList and then setState your newList, so that child ponent can re-render
MyList.forEach((el) => {
if (el.payerName === item.payerName) {
el.state.push(state.value);
if (!el.isActive) {
el.isActive = true;
}
}
});
# also include this
setNewList(MyList)
setUpdate([...update, item]);
}
and use newList everywhere in your child ponent render method.
just get rid of the reference to props.data like so
function EditInsurance(props) {
const [parentData, setParentData] = useState([JSON.parse(JSON.stringify(props.data))]);
...
}
OR
use lodash's clonedeep/clone method
function EditInsurance(props) {
const [parentData, setParentData] = useState([._cloneDeep(props.data))]);
...
}
so the question is why changing data from child ponent changes data of parent ponent?,the reason is objects in js are reference, when you do something like this,
//sets the data onLoad
useEffect(() => {
setList(data);
}, []);
both list of child and data of parent refers to same object in memory,changing something in list will directly reflected to data.and again you are trying to change state of parent ponent from child directly which is not a good practice. now you can make is work by creating new copy of data in child ponent, like this
//sets the data onLoad
useEffect(() => {
setList({...data});
}, []);
but with spread operator inner nodes of data again will be shared with child ponent so better way is to make deep copy using JSON.stringify and JSON.parse
//sets the data onLoad
useEffect(() => {
setList(JSON.parse(JSON.stringify(data)));
}, []);
this way both child and parent ponent will have their own copy of data and changes from child will not be reflected to parent ponent,hope this will help you.
Looking at the other answers, and seeing as you haven't figured out a solution yet, have you tried cloning data
before passing it down as a prop?
Parent Component:
const [data, setData] = useState([]);
const [editing, setEditing] = useState(null);
{!!editing && (
<EditInsurance state={editing} data={[...data]} setEditing={setEditing} />
)}