Обновление объекта с помощью setState в React

266

Можно ли вообще обновить свойства объекта setState?

Что-то вроде:

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

Я пытался:

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

и это:

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

Первое приводит к синтаксической ошибке, а второе просто ничего не делает. Любые идеи?

JohnSnow
источник
5
второй код сработал бы, однако вы бы потеряли ageсвойство внутри jasper.
Гиоргим

Ответы:

580

Есть несколько способов сделать это, так как обновление состояния является асинхронной операцией , поэтому для обновления объекта состояния нам нужно использовать функцию обновления с setState.

1- Самый простой:

Сначала создайте копию, а 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
})

Вместо использования Object.assignмы также можем написать это так:

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

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

Примечание: Object.assign и Spread Operatorсоздает только поверхностную копию , поэтому, если вы определили вложенный объект или массив объектов, вам нужен другой подход.


Обновление вложенного объекта состояния:

Предположим, вы определили состояние как:

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

Чтобы обновить объект extraCheese of pizza:

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

Обновление массива объектов:

Предположим, у вас есть приложение todo, и вы управляете данными в этой форме:

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

Чтобы обновить состояние любого объекта todo, запустите карту в массиве и проверьте наличие уникального значения каждого объекта, в случае condition=trueвозврата нового объекта с обновленным значением, в противном случае - того же объекта.

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

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

}))

Предложение: если объект не имеет уникального значения, используйте индекс массива.

Маянк Шукла
источник
Способ, которым я пытался теперь, работает, хотя ... второй путь: S
JohnSnow
7
@JohnSnow во второй раз он удалит другие свойства из jasperобъекта, так что console.log(jasper)вы увидите только один ключ name, возраст не будет там :)
Mayank Shukla
1
@JohnSnow, да, этот путь уместен, если jasperесть object, лучше или нет, возможно, возможны и другие лучшие решения :)
Mayank Shukla
6
Здесь стоит упомянуть, что ни Object.assignоператоры глубокого копирования, ни распространяемые операторы не распространяются. Таким образом, вы должны использовать обходные пути, такие как lodash deepCopyи т. Д.
Алекс Васильев
1
Как бой let { jasper } = this.state?
Брайан Ле
37

Это самый быстрый и самый читаемый способ:

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

Даже если this.state.jasperуже есть свойство name, name: 'someothername'будет использоваться новое имя .

mannok
источник
39
Это решение имеет один большой недостаток: для оптимизации обновлений состояния React может группировать несколько обновлений. Как следствие this.state.jasper, не обязательно содержать последнее состояние. Лучше использовать обозначения с prevState.
согрешил
33

Используйте оператор распространения и немного ES6 здесь

this.setState({
    jasper: {
          ...this.state.jasper,
          name: 'something'
    }
})
Нихилу
источник
Это обновляет яшму, но другие свойства удаляются из состояния.
IAQ
11

Я использовал это решение.

Если у вас есть вложенное состояние, подобное этому:

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

Вы можете объявить функцию 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);
  }

здесь HTML с прослушивателем событий. Убедитесь, что вы используете то же имя, что и для объекта состояния (в данном случае «friendName»)

<input type="text" onChange={this.handleChange} " name="friendName" />
Альберто Пирас
источник
Это сработало для меня, за исключением того, что я должен был использовать это вместо:statusCopy.formInputs[inputName] = inputValue;
MikeyE
10

Я знаю, что здесь есть много ответов, но я удивлен, что ни один из них не создает копию нового объекта вне setState, а затем просто setState ({newObject}). Чисто, лаконично и надежно. Итак, в этом случае:

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

Или для динамического свойства (очень полезно для форм)

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

colemerrick
источник
7

попробуйте это, должно работать нормально

this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Бурак Кахраман
источник
4
Вы мутируете объект состояния напрямую. Чтобы использовать этот подход, добавьте новый объект в качестве источника:Object.assign({}, this.state.jasper, {name:'someOtherName'})
Albizia
3

Первый случай действительно является синтаксической ошибкой.

Поскольку я не вижу остальной части вашего компонента, трудно понять, почему вы вкладываете объекты в своем состоянии здесь. Не стоит вкладывать объекты в состояние компонента. Попробуйте установить начальное состояние так:

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

Таким образом, если вы хотите обновить имя, вы можете просто позвонить:

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

Достигнет ли это того, к чему вы стремитесь?

Для больших и более сложных хранилищ данных я бы использовал что-то вроде Redux. Но это намного сложнее.

Общее правило с состоянием компонента - использовать его только для управления состоянием пользовательского интерфейса компонента (например, активный, таймеры и т. Д.).

Проверьте эти ссылки:

mccambridge
источник
1
Я использовал это только в качестве примера, объект должен быть вложенным ... Я, вероятно, должен использовать Redux, но я пытаюсь понять основы React ..
JohnSnow
2
Да, я бы пока держался подальше от Redux. Пока вы изучаете основы, постарайтесь сохранить ваши данные простыми. Это поможет вам избежать странных случаев, которые сбивают вас с толку. 👍
МакКембридж
2

Другой вариант: определить вашу переменную из объекта Jasper, а затем просто вызвать переменную.

Оператор спреда: 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
    }
})
Луис Мартинс
источник
2

Простой и динамичный способ.

Это сделает работу, но вам нужно установить все идентификаторы для родителя, чтобы родитель указывал на имя объекта, то есть id = "jasper", и называл имя свойства input element = свойство внутри объекта jasper. ,

handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Алехандро Санчес Дюран
источник
Я согласен с первой строкой, так как это единственное, что сработало для моего объекта из объектов в состоянии! Также стало ясно, где я застрял в обновлении моих данных. Безусловно, самый простой и динамичный способ ... Спасибо!
Смит
2

Вы можете попробовать это : ( Примечание : имя входного тега === поле объекта)

<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 
   })
}
Xuanduong Ngo
источник
Добро пожаловать в переполнение стека. Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, почему и / или как этот код отвечает на вопрос, повышает его долгосрочную ценность. Как ответить
Elletlar
2

это еще одно решение, использующее утилиту immer immutabe, очень легко подходящее для глубоко вложенных объектов, и вам не следует беспокоиться о мутации

this.setState(
    produce(draft => {
       draft.jasper.name = 'someothername
    })
)
samehanwar
источник
1

Также, следуя решению Альберто Пираса, если вы не хотите копировать весь объект «состояние»:

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});
  }
Марк Лаквей
источник
1

Вы можете попробовать это:

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

или для другого имущества:

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

Или вы можете использовать функцию 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}
    })
}

и 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}
/>
Неманья Стоянович
источник
Это работает без JSON.stringify .. this.setState (prevState => {prevState = this.state.document; prevState.ValidDate = event.target.value; prevState.ValidDateFormat = dayFormat; return {document: prevState}}); ..Где документ является объектом государственного типа ..
Олег
1

Без использования Async и Await Используйте это ...

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

Если вы используете с Async и Await, используйте это ...

async funCall(){
      await this.setState({...this.state.jasper, name: 'someothername'});
}
Шантану Шарма
источник
0

Эта настройка работала для меня:

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

this.setState({newState: newState});

console.log(this.state.jasper.name); //someOtherName
Lazza
источник