codego
codego

Reputation: 109

Correct way to update an objects state in react (Merged state? Spread operators?)

I've been using React for a bit now, and I've always updated an object's state using the spread operator as I remember reading you should directly change current state.

E.g now I write something like this:

const changeState = () => {
  setState(prevState => {...prevState, existingKey: 'new value'})
} 

But I've just come across a section "State Updates are Merged" in the react docs (https://reactjs.org/docs/state-and-lifecycle.html#state-updates-are-merged). If I'm understanding it correctly it's essentially saying I can just write this instead:

const changeState = () => {
  setState({existingKey: 'new value'})
} 

and all other keys in the object will remain as is, only the key I have specified will be updated in state. Am I reading this correctly? I've never seen this on stackoverflow, I always see people saying to use spread operators, is there a reason why?

Upvotes: 4

Views: 20055

Answers (3)

Drew Reese
Drew Reese

Reputation: 202836

The reason the functional state update and shallow copying is recommended is that is works for all use cases. The state-updates-are-merged link you reference actually applies to class-based component's this.setState method.

  • Enqueueing multiple updates within a single render cycle, loops, etc...

    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
    

    The net result here is only a single count + 1 update as each update overwrote the previous one.

    this.setState(prevState => ({ count: prevState.count + 1 }));
    this.setState(prevState => ({ count: prevState.count + 1 }));
    this.setState(prevState => ({ count: prevState.count + 1 }));
    

    The net result here is now count + 3.

  • Referencing the previous state in callbacks to avoid stale enclosures.

     componentDidMount() {
       setInterval(() => this.setState({ count:  this.state.count + 1 }), 1000);
     }
    

    Here the initial count state is closed over in callback scope and will never update.

     componentDidMount() {
       setInterval(() => this.setState(prevState => { count:  prevState.count + 1 }), 1000);
     }
    
  • Only the root properties are merged, more deeply nested state being updated also needs to be shallow copied.

     this.setState(prevState => ({
       ...prevState,
       property: {
         ...prevState.property,
         nestedProperty: 'new value',
       },
     }));
    
  • On the off-hand chance you are using the useState hook, state updates are not shallowly merged.

    useState

    Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:

    const [state, setState] = useState({});
    setState(prevState => {
      // Object.assign would also work
      return {...prevState, ...updatedValues};
    });
    

A good rule of thumb

If the next state depends on the previous state in any way, use a functional state update. Otherwise, the regular update is sufficient.

Upvotes: 6

Somnath Jana
Somnath Jana

Reputation: 7

//Use react hooks for the functional component

import React, { useState, useEffect } from 'react';

//Suppose a user object with name, age, gender properties.

let [userData, setUserData] = useState({
    name: "Somnath Jana",
    age:27
    gender: "Male"
})

useEffect(()=>{
   userData.age = 28
   setUserData({...userData})
},[])

Upvotes: -1

John
John

Reputation: 1285

For Functional components, the syntax would be:

const changeState = () => {
  setState(prevState => {...prevState, existingKey: 'new value'})
} 

For Class components, the syntax would be:

const changeState = () => {
  // Notice the use of this
  this.setState({existingKey: 'new value'})
} 

Upvotes: 1

Related Questions