ReactのsetStateによるオブジェクトの更新

setStateでオブジェクトのプロパティを更新することは可能でしょうか?

以下のようなものです。

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

試してみました。

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

とこれ。

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

最初のものは構文エラーになり、2番目のものは何もしません。何かアイデアはありませんか?

ソリューション

これには複数の方法があります。stateの更新はasync operationなので、stateオブジェクトを更新するには、setStateを使ったupdater functionを使う必要があります。

*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- [spread operator]3を使用します。

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.assignSpread Operator は、浅いコピー しか作成しませんので、ネストしたオブジェクトやオブジェクトの配列を定義している場合は、別のアプローチが必要です。


ネストした状態のオブジェクトを更新します。

stateを次のように定義しているとします。

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

pizza オブジェクトの 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
    }
  }
}))

オブジェクトの配列を更新します。

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
  )

}))

提案: オブジェクトがユニークな値を持たない場合は、配列のインデックスを使用します。

解説 (11)

最初のケースは確かにシンタックスエラーです。

あなたのコンポーネントの残りの部分を見ることができないので、なぜここでステートにオブジェクトを入れ子にしているのかわかりません。コンポーネントの状態にオブジェクトを入れ子にするのはよくありません。初期状態をこう設定してみてください。

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

そうすれば、名前を更新したいときには、単に

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

これで狙い通りになるのでしょうか?

より大きくて複雑なデータストアには、Reduxのようなものを使います。しかし、それはもっと高度なものです。

コンポーネントの状態に関する一般的なルールは、コンポーネントのUI状態(アクティブ、タイマーなど)を管理するためにのみ使用することです。

これらのリファレンスをチェックしてみてください。

解説 (2)

もう一つの方法は、Jasperオブジェクトから変数を定義して、変数を呼び出すことです。

*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
    }
})
解説 (1)