【React】useStateの使い方 💡

前回記事[【React】入門してみたまとめ ✍🏻](https://qiita.com/__knm__/items/f1cf893d1b404fdd386f) の続きです。

## useStateとは

変更した値を保持しつつ再レンダリングを可能にする仕組みが`useState()`。
関数コンポーネントでstateを管理（stateの保持と更新）するためのReactフックです。

:::note
state(状態)とは、コンポーネント毎に保持・管理される値。
:::

### useStateを使う際の注意点

* コンポーネント、カスタムフックの中で呼び出す
* if文やfor文の中で呼び出さない
* 値の更新と再レンダリングは予約（非同期）される
* 前回のstate値を使用する場合は更新用関数の引数に関数を渡す
* オブジェクト型のstateを更新する際は新しいオブジェクトを作成する
* stateの値をコンポーネント毎に独立して管理される
* 一度消滅したコンポーネントのstateの値はリセットされる
* stateをpropsとして渡すことで子コンポーネントで利用できる
* コンポーネントの位置によってはstateが維持される

## useStateの使い方

```jsx
import { useState } from "react";
const Example = () => {
  const [val, setVal] = useState("testState");
  return (
    <>
      <input
        type="text"
        onChange={(e) => {
          setVal(e.target.value);
        }}
      />
      : {val}
    </>
  );
};

export default Example;
```

上記関数コンポーネントを実行すると下記のような表示になり、初期値「testState」がinputに入力した値に応じて即時的にレンダリングされます。
![スクリーンショット 2023-01-14 13.05.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/eaaaaeb6-3117-8590-3f8a-04c7ea0af56c.png)

### コード解説

```jsx
import { useState } from "react";
```
useStateを使用するためにはreactライブラリからimportする必要があります。

```jsx
const [val, setVal] = useState("testState");
```
こちらで`useState()`を実行しています。
`useState()`の引数が値の初期値になります。
`useState()`を実行して、返ってくる値は、配列です。
どんな配列かというと、**0番目に参照用の値、1番目に更新用の関数**の配列です。
その返ってきた値を分割代入しています。

試しにconsole出力してみると配列が返ってきていることがわかるかと思います🧑‍🚀
`console.log(useState("初期値"));`
![スクリーンショット 2023-01-14 13.17.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/ff65b013-e479-5771-7eed-e18f828164af.png)

```jsx
<input
    type="text"
    onChange={(e) => {
      setVal(e.target.value);
    }}
/>
  : {val}
```
そしてこちらのjsx部分では、`onChange`でinputに更新があるたびに、`setVal()`、つまり分割代入した更新用の関数が実行されています。
`setVal()`の引数には新しい値を渡します。
今回は`e.target.value`でinputに入力したvalueを渡しています。
そしてこの`setVal()`は実行されると関数コンポーネントの再実行、つまり画面の再レンダリングが発生します🌐
` : {val}`で初期値が表示されており、`setVal(e.target.value)`によって`val`の値が`e.target.value`に更新され、再レンダリングされることによって表示が変わっているという流れです。

## オブジェクト型の値をステートしたい

オブジェクト型の値をステートしたい場合においても基本的には同じ使い方です。

```jsx
import { useState } from "react";

const Example = () => {
  const sampleObj = { name: "Sato", age: 26 };
  const [obj, setObj] = useState(sampleObj);
  const ChangeName = (e) => {
    setObj({
      name: e.target.value,
      age: obj.age,
    });
  };
  const ChangeAge = (e) => {
    // setObj({
    //   name: obj.name,
    //   age: e.target.value,
    // });

    // ↑をスプレッド演算子で書いた場合。これで、objオブジェクトがオブジェクトとして展開されることになり、更新したいプロパティだけ「,」に続けて書く。
    setObj({
      ...obj,
      age: e.target.value,
    });
  };
  const reset = () => {
    setObj({
      name: "",
      age: "",
    });
  };
  return (
    <>
      <p>Name: {obj.name}</p>
      <p>Age: {obj.age}</p>
      <input type="text" value={obj.name} onChange={ChangeName} />
      <input type="number" value={obj.age} onChange={ChangeAge} />
      <button onClick={reset}>リセット</button>
    </>
  );
};

export default Example;
```

![スクリーンショット 2023-01-14 17.20.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/6832b0ce-ea08-980c-c760-82bc0fa8a020.png)

ChangeAgeなどの`setObj()`で新しいstateの値となるオブジェクトを記述しているのがわかります。
```jsx
setObj({
      name: obj.name,
      age: e.target.value,
    });
```
オブジェクトをステートしたい場合においても、初期値と同じ形(今回はオブジェクトなのでオブジェクト)で新しい更新用の値を引数として渡せばいいだけです。
今回のサンプルではプロパティがname,ageの二つしかないですが、もしプロパティが10個あったらいちいち全てを記述するのは面倒です🤦
そこでスプレッド演算子を記述します。下記のコードで上記コードと同じ動作をします。

```jsx
 setObj({
      ...obj, 
      age: e.target.value,
    });
```
`...obj`のスプレッド演算子部分で、objオブジェクトが展開され、「,」に続けて値を更新したいプロパティのみを記述します。

### 配列型の値をステートしたい

配列の値は連結され描画されます。

```jsx
import { useState } from "react";
const Example = () => {
  const numArray = [1, 2, 3, 4, 5];
  const [nums, setNums] = useState(numArray);

  const shuffle = () => {
    const newNums = [...nums]; // スプレッド演算子で既存のnums配列を展開し新しい配列を作成
    const value = newNums.pop();
    newNums.unshift(value);
    setNums(newNums);
  };
  return (
    <>
      {nums}
      <button onClick={shuffle}>シャッフル</button>
    </>
  );
};

export default Example;
```
![スクリーンショット 2023-01-14 17.51.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2609332/16b1c493-ee90-2ca5-1ccc-895ab91afd27.png)

`const newNums = [...nums];`部分でスプレッド演算子で既存の値を展開し新しい値を作成しています。
この`newNums`をコネコネして、新しい値として`setNums`に渡しています。
`newNums`のような新しい変数を作成しなくても`nums`をコネコネすればいいような気がしますが、それでは動作しなかったです..。
**更新用の新しい値は必ず初期のオブジェクトと異なる必要がある**ようです。
これはオブジェクト型をステートする場合においても同様です。