Updating objects in a State. How not to use spread operator.

Using Immer package to simplify updating objects in state.

Updating objects in a State. How not to use spread operator.

Photo by Irvan Smith on Unsplash

Understanding state and mutating object.

To add interactivity, we use state in react, the UI changes only when the state changes, when you set a new state, not by mutating the state.

That's why you shouldn't change objects that you have stored in react state directly. Mutating the state that way changes the object in memory, but that does not affect UI.

Instead, you create a new one or make a copy and use it to set the new state.

The way UI changes when you change the state is the setState function triggers a re-render to display a new UI.

Let's take an example.

const [position, setPosition] = useState({ x: 0, y: 0 });

You can mutate this by setting position.x = 5;

But that won't trigger re-render and change UI. You must treat react state as read-only to understand this.

You can do like this below:

const nextPosition = {};
nextPosition.x = 5;
nextPosition.y = 8;
setPosition(nextPosition);

// OR

setPosition({
    nextPosition.x = 5,
    nextPosition.y = 8
})

Copying objects using spread syntax.

In the previous example, the position object is always created new from the current position. But, sometimes you want to change only one value in the object and keep existing data as it is.

Let's see another example.

const [person, setPerson] = useState({
    firstName: 'Rohan',
    lastName: 'Joshi',
    email: 'rohan@aib.com'
  });

When you want to change only his lastName when an event occurs you can do

setPerson({
  ...person, // Copy the old fields using ...(the spread operator)
  lastName: e.target.value // But override this one
});

// Instead of doing
setPerson({
  firstName: person.lastName, 
  lastName: e.target.value, // New last name from the input
  email: person.email
});

This is very helpful when you have a form in the UI and you want to keep storing inputs from the form simple.

For forms, may it be a small form or a large form, keeping all form data grouped in an object is very convenient—as long as you update it correctly!

Cons of using the Spread Operator

The ... spread operator is "SHALLOW"! Took me a while to understand what shallow means. Let me explain it to you in simple words.

It only copies things one level deep. What does it mean?

It means it copies only that object and not the objects nested in that object. Why?

For example... ...obj only copies obj object. Both copied and existing objects point to the same channel object.

let obj = {
  name: 'TanMy Butt',
  channel: {
    title: 'Tanmay Bhatt',
    genre: 'comedy',
    source: 'YouTube'
  }
};

Well, actually nesting would be the wrong word to use here. There is no such thing as a "nested" object here. These two objects are two different individual objects.

Let's understand it with an example.

let obj1 = {
    title: 'Tanmay Bhatt',
    genre: 'comedy',
    source: 'YouTube'
  }

let obj2 = {
  name: 'TanMy Butt',
  channel: obj1
};

let obj3 = {
  name: 'Kullu',
  channel: obj1
};

Both the obj3 and obj2 are pointing at the same object as the value of the channel key.

When you do obj3.channel.source = "Twitter", it also changes obj2.channel.source and obj1.source to "Twitter".

Now you understand there's nothing like nested objects, Instead separate objects pointing at each other with properties.

Using Immer package to simplify updating objects in state.

Sometimes using the spread operator could be cumbersome when it comes to updating values of properties nested deep in an object.

Like for example this:

setTree({
    ...tree, 
        branches: {
            ...tree.branches,
                leeves: {
                     ...tree.branches.leeves,
                     count: 234;
                }
            }
})

// Which could be done easily if you were to mutate the object like below
tree.branches.leeves.count = 234;

If your state is deeply nested, you might want to consider flattening it.

Immer is a popular library that lets you write using the convenient but mutating syntax(like shown above) and takes care of producing the copies for you.

To try Immer:

  1. Run npm install use-immer to add Immer as a dependency

  2. Then replace import { useState } from 'react' with import { useImmer } from 'use-immer'

the setTree function above could be replaced with the updateTree function from useImmer below:

const [tree, updateTree] = useImmer({
    name: 'Oak Tree',
    branches: {
      count: 43,
      leeves: {
            count: 324
        }
    }
  });

updatePerson(draft => {
      draft.branches.leeves.count = 234;
    });

You see the difference? Using Immer can save you from spread syntax hell.

You can use both useState and useImmer in a component, whichever suits you and you're comfortable with using it.