cbdeveloper
cbdeveloper

Reputation: 31485

React - Updating state (array of objects). Do I need to deep copy the array?

I have the following state:

const[images,setImages] = useState([
  {src: 'stringSRC1', selected: false},
  {src: 'stringSRC2', selected: false},
  {src: 'stringSRC3', selected: false}
]);

I'm updating it (toggling selected state) with this code:

function handleImageClick(index) {
  props.setImages((prevState)=>{
    const aux = Array.from(prevState);
    aux[index].selected = !aux[index].selected;
    return aux;
  });
}

It works as intended. But I've thought of one thing.

When I'm copying the array from prevState, I'm creating a new array, but the objects (stored as references) will remain the same. I've tested and they do not change when you copy the array like that.

QUESTION

Is this a bad practice? Should I bother to deep copy the array, as in create a new array and create brand new objects? Or this is just fine?

Upvotes: 13

Views: 5979

Answers (3)

rangfu
rangfu

Reputation: 8967

TL;DR - You still need to make deep copies.

The official docs recommend making deep copies:

State can hold any kind of JavaScript value, including objects. But you shouldn’t change objects that you hold in the React state directly. Instead, when you want to update an object, you need to create a new one (or make a copy of an existing one), and then set the state to use that copy.

They also suggest making your state objects as flat as possible to make deep copying easier and if that's not an option, using something like immer to handle the deep-copying for you.

If your state is deeply nested, you might want to consider flattening it. But, if you don’t want to change your state structure, you might prefer a shortcut to nested spreads. Immer is a popular library that lets you write using the convenient but mutating syntax and takes care of producing the copies for you.

Upvotes: 2

F.bernal
F.bernal

Reputation: 2694

Yes, you must deep clone, if you want to avoid side effects.

In your function:

function handleImageClick(index) {
  props.setImages((prevState)=>{
    const aux = Array.from(prevState);
    aux[index].selected = !aux[index].selected;
    return aux;
  });
}

As you are using Array.from you are getting references to the objects stored, so your prevState is modified.

See this:

setTest(prevState => {
      console.log("PrevState", prevState);
      const b = Array.from(prevState);
      b[0].a = b[0].a + 1;
      return b;
    });

You can test it here:

https://codesandbox.io/embed/exciting-mcnulty-6spdh?fontsize=14

You will see that prevState has the next value, so you lose your prevState value.

Imagine that you want to do this in your code:

function handleImageClick(index) {
      props.setImages((prevState)=>{
        const aux = Array.from(prevState);
        aux[index].selected = !aux[index].selected;
        aux[index + 1].selected = prevState[index].selected;
        return aux;
      });
    }

aux[index + 1].selected = prevState[index].selected; is broken!!

So you need to deep copy the array to avoid this kind of things.

You can do: const aux = JSON.parse(JSON.stringify(prevState));

Upvotes: -1

Dupocas
Dupocas

Reputation: 21347

No, this is how it's supposed to work. Yeap, the objects are not changing, but the important here is that the array changes. React performs shallow comparations between the DOM and the virtual DOM to now when things changed. Once your new array is compared with the old one the change will be detected, the content of the array itself doesn't really matter.

Upvotes: 1

Related Questions