I am making changes to a set of data and the data is held in a parent components state I pass that data to a 2nd component but when I make changes to the data in the 2nd component 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 component 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 component 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 component that doesn't manipulate the data in the Parent?
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]);
};
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 component 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 component, you can do something like:
function EditInsurance(props) {
const [parentData, setParentData] = useState([...props.data]);
...
}
And then use the parentData on your child component, you don't even need the useEffect.
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 component 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 component 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 component render method.