ChatGPT解决这个技术问题 Extra ChatGPT

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?

second code would have worked however you would have lost the age property inside jasper.
I understand that React uses .assign() to. merge the old state object with the new object so shouldn't the second code work properly?

M
Mayank Shukla

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.


@JohnSnow in you second it will remove the other properties from jasper object, do the console.log(jasper) you will see only one key name, age will not be there :)
@JohnSnow, yes this way is proper if jasper is an object, don't the whether best or not, may be other better solutions are possible :)
Good to mention here that neither Object.assign nor spread operator deep copy properties. In this way you should use workarounds like lodash deepCopy etc.
How bout let { jasper } = this.state?
@ankur_rajput we use spread operator to create a shallow copy, you can use Object.assign as well. Avoid the direct mutation of the state object.
T
Tim Gerhard

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.


This solution has one big disadvantage: in order to optimize state updates React might group multiple updates. As a consequence this.state.jasper does not necessarily contain the latest state. Better use the notation with the prevState.
J
Just code

Use spread operator and some ES6 here

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

This updates jasper but other properties are removed from the state.
c
colemerrick

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 }))


because you've used two statements where only one is required.
r
ravibagul91

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" />

This worked for me, except I had to use this instead: statusCopy.formInputs[inputName] = inputValue;
B
Burak Kahraman

try this,it should work fine

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

You are mutating the state object directly. To use this approach, add a new object as the source : Object.assign({}, this.state.jasper, {name:'someOtherName'})
R
Ryan Dantzler

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"
  }
}));

m
mccambridge

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:

https://facebook.github.io/react/docs/react-component.html#state

https://facebook.github.io/react/docs/state-and-lifecycle.html


I only used that as an example, the object must be nested.. I probably should be using Redux but I am trying to understand React fundementals..
Yeah, I would stay away from Redux for now. While you're learning the fundamentals, try to keep your data simple. That'll help you avoid odd cases that trip you up. 👍
s
samehanwar

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'
    })
)

h
himanshu sharma

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
    }
  }))
}

R
Rajesh Nasit

Using hook we can do following way

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

J
J. Steen

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
    }
})

N
Nemanja Stojanovic

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}
/>

This works without JSON.stringify .. this.setState(prevState => { prevState = this.state.document; prevState.ValidDate = event.target.value; prevState.ValidDateFormat = dayFormat; return {document: prevState} }); ..Where document is state type object..
A
Alejandro Sanchez Duran

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 } });

I agree with first line, as this is the only thing that worked for my object of objects in state! Also it cleared where I was stuck in updating my data. By far the most simple and dynamic way... Thanks !!
S
Shantanu Sharma

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'});
}

r
ravibagul91

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 
   })
}

Welcome to Stack Overflow. While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. How to Answer
A
Anurag Tripathi

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.


M
Marc LaQuay

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});
  }

D
David Jesus

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

P
Peppe426

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 };
        });
    }

L
LaZza

This setup worked for me:

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

this.setState({newState: newState});

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

n
nolan

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')})

S
Shahnad

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 }
    
        })
      }