'Updating an object with setState in React

Is it at all possible to update object's properties with setState?

Something like:

this.state = {
   jasper: { name: 'jasper', age: 28 },
}

I have tried:

this.setState({jasper.name: 'someOtherName'});

and this:

this.setState({jasper: {name: 'someothername'}})

The first results in a syntax error and the second just does nothing. Any ideas?



Solution 1:[1]

There are multiple ways of doing this, since state update is a async operation, so to update the state object, we need to use updater function with setState.

1- Simplest one:

First create a copy of jasper then do the changes in that:

this.setState(prevState => {
  let jasper = Object.assign({}, prevState.jasper);  // creating copy of state variable jasper
  jasper.name = 'someothername';                     // update the name property, assign a new value                 
  return { jasper };                                 // return new object jasper object
})

Instead of using Object.assign we can also write it like this:

let jasper = { ...prevState.jasper };

2- Using spread syntax:

this.setState(prevState => ({
    jasper: {                   // object that we want to update
        ...prevState.jasper,    // keep all other key-value pairs
        name: 'something'       // update the value of specific key
    }
}))

Note: Object.assign and Spread Operator creates only shallow copy, so if you have defined nested object or array of objects, you need a different approach.


Updating nested state object:

Assume you have defined state as:

this.state = {
  food: {
    sandwich: {
      capsicum: true,
      crackers: true,
      mayonnaise: true
    },
    pizza: {
      jalapeno: true,
      extraCheese: false
    }
  }
}

To update extraCheese of pizza object:

this.setState(prevState => ({
  food: {
    ...prevState.food,           // copy all other key-value pairs of food object
    pizza: {                     // specific object of food object
      ...prevState.food.pizza,   // copy all pizza key-value pairs
      extraCheese: true          // update value of specific key
    }
  }
}))

Updating array of objects:

Lets assume you have a todo app, and you are managing the data in this form:

this.state = {
  todoItems: [
    {
      name: 'Learn React Basics',
      status: 'pending'
    }, {
      name: 'Check Codebase',
      status: 'pending'
    }
  ]
}

To update the status of any todo object, run a map on the array and check for some unique value of each object, in case of condition=true, return the new object with updated value, else same object.

let key = 2;
this.setState(prevState => ({

  todoItems: prevState.todoItems.map(
    el => el.key === key? { ...el, status: 'done' }: el
  )

}))

Suggestion: If object doesn't have a unique value, then use array index.

Solution 2:[2]

This is the fastest and the most readable way:

this.setState({...this.state.jasper, name: 'someothername'});

Even if this.state.jasper already contains a name property, the new name name: 'someothername' with be used.

Solution 3:[3]

Use spread operator and some ES6 here

this.setState({
    jasper: {
          ...this.state.jasper,
          name: 'something'
    }
})

Solution 4:[4]

I know there are a lot of answers here, but I'm surprised none of them create a copy of the new object outside of setState, and then simply setState({newObject}). Clean, concise and reliable. So in this case:

const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))

Or for a dynamic property (very useful for forms)

const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))

Solution 5:[5]

I used this solution.

If you have a nested state like this:

this.state = {
  formInputs:{
    friendName:{
      value:'',
      isValid:false,
      errorMsg:''
    },
    friendEmail:{
      value:'',
      isValid:false,
      errorMsg:''
    }
  }
}

you can declare the handleChange function that copy current status and re-assigns it with changed values

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let statusCopy = Object.assign({}, this.state);
    statusCopy.formInputs[inputName].value = inputValue;

    this.setState(statusCopy);
  }

here the html with the event listener. Make sure to use the same name used into state object (in this case 'friendName')

<input type="text" onChange={this.handleChange} " name="friendName" />

Solution 6:[6]

try this,it should work fine

this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));

Solution 7:[7]

The first case is indeed a syntax error.

Since I can't see the rest of your component, it's hard to see why you're nesting objects in your state here. It's not a good idea to nest objects in component state. Try setting your initial state to be:

this.state = {
  name: 'jasper',
  age: 28
}

That way, if you want to update the name, you can just call:

this.setState({
  name: 'Sean'
});

Will that achieve what you're aiming for?

For larger, more complex data stores, I would use something like Redux. But that's much more advanced.

The general rule with component state is to use it only to manage UI state of the component (e.g. active, timers, etc.)

Check out these references:

Solution 8:[8]

this is another solution using immer immutabe utility, very suited for deeply nested objects with ease, and you should not care about mutation

this.setState(
    produce(draft => {
       draft.jasper.name = 'someothername'
    })
)

Solution 9:[9]

In case of updating an object where keys are string

e.g. let say your state object is

serviceDays: {
    Sunday: true,
    Monday: true,
    Tuesday: false,
    Wednesday: true,
    Thurday: false,
    Friday: true,
    Saturday: true
  }

so you can update in following way

const onDayClick = day => {
  const { serviceDays } = this.state
  this.setState(prevState => ({
    serviceDays: {
      ...prevState.serviceDays,
      [day]: serviceDays[day] ? false : true
    }
  }))
}

Solution 10:[10]

Using hook we can do following way

const [student, setStudent] = React.useState({name: 'jasper', age: 28});
 setStudent((prevState) => ({
          ...prevState,
          name: 'newName',
        }));

Solution 11:[11]

Another option: define your variable out of the Jasper object and then just call a variable.

Spread operator: ES6

this.state = {  jasper: { name: 'jasper', age: 28 } } 

let foo = "something that needs to be saved into state" 

this.setState(prevState => ({
    jasper: {
        ...jasper.entity,
        foo
    }
})

Solution 12:[12]

You can try with this:

this.setState(prevState => {
   prevState = JSON.parse(JSON.stringify(this.state.jasper));
   prevState.name = 'someOtherName';
   return {jasper: prevState}
})

or for other property:

this.setState(prevState => {
   prevState = JSON.parse(JSON.stringify(this.state.jasper));
   prevState.age = 'someOtherAge';
   return {jasper: prevState}
})

Or you can use handleChage function:

handleChage(event) {
   const {name, value} = event.target;
    this.setState(prevState => {
       prevState = JSON.parse(JSON.stringify(this.state.jasper));
       prevState[name] = value;
       return {jasper: prevState}
    })
}

and HTML code:

<input 
   type={"text"} 
   name={"name"} 
   value={this.state.jasper.name} 
   onChange={this.handleChange}
/>
<br/>
<input 
   type={"text"} 
   name={"age"} 
   value={this.state.jasper.age} 
   onChange={this.handleChange}
/>

Solution 13:[13]

Create a state object

this.state = {
  objName: {
    propertyOne: "",
    propertyTwo: ""
  }
};

Update state using setState

this.setState(prevState => ({
  objName: {
    ...prevState.objName,
    propertyOne: "Updated Value",
    propertyTwo: "Updated value"
  }
}));

Solution 14:[14]

Simple and dynamic way.

This will do the job, but you need to set all the ids to the parent so the parent will point to the name of the object, being id = "jasper" and name the name of the input element = property inside of the object jasper.

handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });

Solution 15:[15]

Without using Async and Await Use this...

funCall(){    
     this.setState({...this.state.jasper, name: 'someothername'});
}

If you using with Async And Await use this...

async funCall(){
      await this.setState({...this.state.jasper, name: 'someothername'});
}

Solution 16:[16]

You can try with this: (Note: name of input tag === field of object)

<input name="myField" type="text" 
      value={this.state.myObject.myField} 
     onChange={this.handleChangeInpForm}>
</input>

-----------------------------------------------------------
handleChangeInpForm = (e) => {
   let newObject = this.state.myObject;
   newObject[e.target.name] = e.target.value;
   this.setState({
     myObject: newObject 
   })
}

Solution 17:[17]

Using hooks in Functional Component:

const [state, setState] = useState({jasper: { name: 'jasper', age: 28 }})
const nameChangeHandler = () => {
      setState(prevState => ({
            ...prevState,
            prevState.jasper.name = "Anurag",
            prevState.jasper.age = 28
      })
    )
}

In these cases It is recommended to use callback-based approach to update the state , because using this approach it is ensured that previously states are fully updated and we're updating based on previously updated state.

Solution 18:[18]

Also, following Alberto Piras solution, if you don't want to copy all the "state" object:

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let jasperCopy = Object.assign({}, this.state.jasper);
    jasperCopy[inputName].name = inputValue;

    this.setState({jasper: jasperCopy});
  }

Solution 19:[19]

Try with this:

const { jasper } = this.state; //Gets the object from state
jasper.name = 'A new name'; //do whatever you want with the object
this.setState({jasper}); //Replace the object in state

Solution 20:[20]

By using the input html input name attribute we can have a more dynamic approach in order to update an object properties.

DOM html input name attribute

<input type="text" name="fname" handleChange={(e: any) => { updatePerson(e) }}/>
<input type="text" name="lname" handleChange={(e: any) => { updatePerson(e) }}/>

React / TSX object.assign

const [person, setPerson] = useState<IPerson>({});

   function updatePerson(e: React.ChangeEvent<HTMLInputElement>): void {
        const { name, value } = e.currentTarget;

        setPerson(prevState => {
            const newState = Object.assign(person, { [name]: value })
            return { ...prevState, ...newState };
        });
    }

Solution 21:[21]

This setup worked for me:

let newState = this.state.jasper;
newState.name = 'someOtherName';

this.setState({newState: newState});

console.log(this.state.jasper.name); //someOtherName

Solution 22:[22]

Your second approach doesn't work because {name: 'someothername'} equals {name: 'someothername', age: undefined}, so theundefined would overwrite original age value.

When it comes to change state in nested objects, a good approach would be Immutable.js

this.state = {
  jasper: Record({name: 'jasper', age: 28})
}

const {jasper} = this.state
this.setState({jasper: jasper.set(name, 'someothername')})

Solution 23:[23]

Sample FC:

   const [formData, setformData] = useState({
           project_admin_permissions: {
              task_forms: false,
              auto_assign_rules: false,
              project_notes: true,
              alerts: false,
              update_criteria: true,
              project_flow: false,
              reports: false,
            }
        
          })
    
     const handleChangeCheckBox = (e) => {
       setformData({
          ...formData, project_admin_permissions: { ...formData.project_admin_permissions, [e.target.name]: e.target.checked }
    
        })
      }