1

I'm using react/redux to fetch data etc.

const dates = useSelector((state) => state.holidays.holidays);
const [newDates, setNewDates] = useState(null);

useEffect(() => {
    dispatch(getAllHolidays());
}, []);

useEffect(() => {
    if (dates) {
        setNewDates([...dates]);
    }
}, [dates]);  

So I'm using the "useSelector" to get the data from my Redux/state component. Then i'm using a "useState" to keep a COPY of the dates in there. The first "useEffect" will get the data I need on component mount/start.
The second "useEffect" will fill in the COPY of dates, every time dates changes (in state).

Anyway, whenever I change "newDates" value now, and do a console.log of the "dates" array, everything's changed as well... I thought using the spreader operator would create a new array (so no previous reference to the "dates"-array, and then fill it with all objects of the "dates"-array. But seemingly it's not the case?

For example, here's how I use it:

const handleDateDescriptionChange = (id, event) => {
    let newDatesArray = [...newDates];
    newDatesArray.find(date => date.id === id).description = event.target.value;
    console.log(dates.find(x => x.id === id).description);
    setNewDates([...newDatesArray]);
}

const deleteDateChanges = (id) => {
    newDates.find(x => x.id === id).description = dates.find(x => x.id === id).description;
    console.log(newDates.find(x => x.id === id).description);
    console.log(dates.find(x => x.id === id).description);
    setNewDates([...newDates]);
}  

In the first function, I want to change the value of the object (with corresponding ID) to set it's value of it's "description" to whatever the user typed in in a TextField. So just to be sure, I create a new array copy (no reference to previous) locally, then find the object I need (based on id), then change it's "description" value.

Then I console.log the original "dates" array, and it's modified too!!

In the second function I'm trying to reset the modified "description" value to the original value from the "dates"-array, but it's not possible because the "dates"-array was changed also and the values are the same, so the second function does nothing now, functionally.

Am I missing something here?!

Tempuslight
  • 1,004
  • 2
  • 17
  • 34
  • The spread syntax only performs a shallow copy. As you have objects inside your array you need to copy each individual object within your array as well (see: [How do you clone an Array of Objects in Javascript?](https://stackoverflow.com/a/40283265)) – Nick Parsons Jun 22 '21 at 09:12
  • Ah yes, I was so steadfast in my belief thinking the spreader operator was creating a "hard copy", i.e. with it's own reference scope etc... As another comment pointed out, I just have to hard copy each object in a loop and then it worked :) thanks for the comment! – Tempuslight Jun 22 '21 at 09:22
  • 1
    Copying Redux state into a local React component state goes against Redux paradigm. It creates multiple sources of truth and is not necessary... You should directly use the data returned by your selector. – Ernesto Stifano Jun 22 '21 at 09:30

2 Answers2

1

This is because an array is stored in the heap. Arrays are reference types, therefore if you try to assign an array to a new array, the new array won't be a copy. It will reference to the array storage in the heap.

On of the easiest way to real copy an array is to loop through the array and assign every value by itself to the new array. Therefore the new array get it's own heap storage place but just with the same values as the original one.

Here is a good explanation if you are more interested in this topic: Link

Rendolph
  • 433
  • 2
  • 9
  • 1
    This works, I was so steadfast into thinking the spreader operator created it's own "scope/reference" if you will, but someone also pointed out it's only a shallow copy... I've "hard copied" every object in a for loop and now it works, thanks!! – Tempuslight Jun 22 '21 at 09:21
0

Shallow copying only works are at the root level of the operation, all more deeply nested objects are still references back to the originals. You must shallow copy all nested state that is being updated to avoid state mutations.

const handleDateDescriptionChange = (id, event) => {
  // use array.map to shallow copy the array
  setNewDates(dates => dates.map(date => date.id === id ? {
    ...date, // <-- shallow copy date if id match
    description: event.target.value
  } : date)); // <-- otherwise return original since no update
}

const deleteDateChanges = (id) => {
  // use array.filter to shallow copy array and remove elements
  setNewDates(dates => dates.filter(date => date.id !== id));
}  
Drew Reese
  • 165,259
  • 14
  • 153
  • 181