简体   繁体   English

在 React 中使用 setState 更新对象

[英]Updating an object with setState in React

Is it at all possible to update object's properties with setState ?是否可以使用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?有任何想法吗?

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 .有多种方法可以做到这一点,因为状态更新是异步操作,所以要更新状态对象,我们需要使用带有setStateupdater 函数

1- Simplest one: 1-最简单的一个:

First create a copy of jasper then do the changes in that:首先创建一个jasper的副本,然后在其中进行更改:

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:除了使用Object.assign ,我们还可以这样写:

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

2- Using spread syntax : 2-使用传播语法

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.注意: Object.assignSpread Operator仅创建浅拷贝,因此如果您定义了嵌套对象或对象数组,则需要不同的方法。


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:要更新比萨对象的 extraCheese:

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.要更新任何 todo 对象的状态,请在数组上运行映射并检查每个对象的某些唯一值,如果condition=true ,则返回具有更新值的新对象,否则返回相同的对象。

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.建议:如果对象没有唯一值,则使用数组索引。

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.state.jasper已经包含一个 name 属性,新名称name: 'someothername'也会被使用。

Use spread operator and some ES6 here在这里使用扩展运算符和一些 ES6

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

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}).我知道这里有很多答案,但我很惊讶他们都没有在 setState 之外创建新对象的副本,然后只是 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 }))

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 函数

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.这里是带有事件监听器的 html。 Make sure to use the same name used into state object (in this case 'friendName')确保使用与状态对象相同的名称(在本例中为“friendName”)

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

试试这个,它应该可以正常工作

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

Create a state object创建状态对象

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

Update state using setState使用setState更新状态

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

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.对于更大、更复杂的数据存储,我会使用 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 (eg active, timers, etc.)组件状态的一般规则是仅使用它来管理组件的 UI 状态(例如活动、计时器等)

Check out these references:查看这些参考资料:

this is another solution using immer immutabe utility, very suited for deeply nested objects with ease, and you should not care about mutation这是使用immer immutabe 实用程序的另一种解决方案,非常适合轻松嵌套的对象,您不应该关心突变

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

In case of updating an object where keys are string如果更新键为字符串的对象

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

Using hook we can do following way使用钩子我们可以做以下方式

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

Another option: define your variable out of the Jasper object and then just call a variable.另一种选择:从Jasper对象中定义变量,然后调用一个变量。

Spread operator: ES6扩展运算符: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
    }
})

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 功能:

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:和 HTML 代码:

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

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

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.这将完成这项工作,但您需要将所有 id 设置为父级,以便父级将指向对象的名称,即 id = "jasper" 并命名输入元素的名称 = 对象内的属性 jasper .

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

Without using Async and Await Use this...不使用 Async 和 Await 使用这个...

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

If you using with Async And Await use this...如果您与 Async And Await 一起使用,请使用此...

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

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.在这些情况下,建议使用基于回调的方法来更新状态,因为使用这种方法可以确保之前的状态完全更新,并且我们正在根据之前更新的状态进行更新。

Also, following Alberto Piras solution, if you don't want to copy all the "state" object:此外,如果您不想复制所有“状态”对象,请遵循 Alberto Piras 解决方案:

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

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

By using the input html input name attribute we can have a more dynamic approach in order to update an object properties.通过使用 input html 输入名称属性,我们可以采用更动态的方法来更新对象属性。

DOM html input name attribute DOM html 输入名称属性

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

React / TSX object.assign反应/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 };
        });
    }

This setup worked for me:此设置对我有用:

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

this.setState({newState: newState});

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

Your second approach doesn't work because {name: 'someothername'} equals {name: 'someothername', age: undefined} , so the undefined would overwrite original age value.您的第二种方法不起作用,因为{name: 'someothername'}等于{name: 'someothername', age: undefined} ,因此undefined会覆盖原始年龄值。

When it comes to change state in nested objects, a good approach would be Immutable.js当涉及到改变嵌套对象的状态时,一个好的方法是Immutable.js

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

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

Sample FC:示例 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 }
    
        })
      }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM