【React】useStateの基本と利用時の注意点まとめ

# ■stateとは

**state** = 状態のこと。


# ■useStateとは

Reactデフォルトで提供する関数の1つ。
コンポーネント内で状態管理をしたい変数をこの`useState()`を利用して宣言する。
<br>

# ■useStateの特徴
特定の値を保持・管理することができるのが特徴。
<br>
具体例として、inputフォームに入力した値を即座に出力するシステムが挙げられる。

```react
const Example = () => {
  let value;

  return (
    <>
      <input type="text" onChange={(e) => { 
　　　　　　　　　　　　console.log(e.target.value);
　　　　　　　　　　　　value = e.target.value;}}/>
      = {value}
    </>
  )
}
```
上記のようなExampleコンポーネントを作成し、inputに入力された際に入力値を変数valueに代入し、表示するというソースを実行してみる。

![スクリーンショット 2022-12-17 20.34.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/8c138161-2924-51de-49c4-968cf0ec975f.png)


結果は、consoleには出力されるものの、画面には表示されない。
理由は以下の2点。

**①inputタグの外の`{value}`は、Exampleコンポーネントを再実行（再レンダリング）されないと反映されない。**

**②変数valueは、Exampleコンポーネントが再レンダリングする際に一番最初の宣言で初期化されるので、value変数は値が保持されず、常に何もないままである。**
<br>
つまり入力した値を画面に表示するには、更新された値をどこかに保存しておく必要がある。
これを可能にするのが、`useState`である。
## ■使い方

**■基本構文**
```react
import { useState } from 'react';

const [value, setValue] = useState("");
```
<br>

`useState()`の引数には、初期値を代入する。

また、`useState()`の戻り値は以下のようになっている。
<br>
```React
[
  string,  // 読み込み用の値
  React.Dispatch<React.SetStateAction<number>> // 変更用の関数
]
```

**・第一引数　＝　読み込み用の値**
**・第二引数　＝　変更用の関数**

※第一引数の型は必ずstringなわけではなく、useState宣言の際、初期値に何を設定したかによって変わる。
<br>
`useState`関数を実行した際には、
`読み込み用の変数と、その変数を更新する用の関数を持った配列`が戻り値となっている。


先ほどの基本構文、
```react
const [value, setValue]
```

というのは、`useState`の戻り値を分割代入していることを表している。
よって、以下のように取得することも可能。

```react
const value = useState("");

value[0] // 読み込み用変数へのアクセス
value[1] // 更新用関数へのアクセス
```
<br>
しかし、この記述の仕方は基本使用されず、分割代入で書くことが基本。
<br>
<br>

# ■useStateを用いた具体例

先ほどの入力した値を表示する例を、`useState`を利用したパターンに変えると以下の通り。

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

export default function App() {
  const [value, setValue] = useState("");

  const changeValue = (e) => {
    setValue(e.target.value);
  }

  return (
    <>
      <input
        type="text"
        onChange={changeValue}
      />
      = {value}
    </>
  );
}
```

![スクリーンショット 2022-12-17 21.16.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/317abe73-cbb4-2c3b-418c-ac78e33fb817.png)
<br>
ここで、コンポーネントのトップにconsole.logを追加してみると、以下のようなことが起こる。
![スクリーンショット 2022-12-17 22.15.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/703f5d12-b42c-56b6-ea06-27db2a5d6576.png)

レンダリングというconsole出力が10回も出力されている。

この結果から、inputタグに文字を入力する度にコンポーネントが再レンダリングされていることがわかる。

## なぜ再レンダリングが起きるのか

これは、`useState`のsetStateが以下の2つの役割をしているから。

**①読み取り専用変数の更新する。**

**②Reactに対し、関数コンポーネントを再レンダリングするよう呼びかける。**
<br>
この2つ目の役割があることにより、コンポーネントが再レンダリングされる。
<br>

# ■useStateの注意点

## ①useStateはコンポーネントのトップレベルでしか宣言できない。

トップレベルとは、関数コンポーネント内の`{}`の中のこと。
```react
// OK
const Example = () => {
   const [value, setaValue] = useState();
   ...
}

// NG 1
const [value, setaValue] = useState();
const Example = () => {
   ...
}

// NG 2
const Example = () => {
  if() { // for文やwhile文も同様にNG
   const [value, setaValue] = useState();
  }
   ...
}
```

## ②更新は非同期で行われる

前の説明で、setStateは読み取り専用変数の更新を行うと説明したが、この更新は即座には行わない。

例として、以下のソースを実行してみる。
```react
import "./styles.css";
import { useState } from "react";

export default function App() {
  const [value, setValue] = useState(0);

  const changeValue = () => {
    setValue(value + 1);
    setValue(value + 1);
    console.log(value);
  };

  return (
    <>
      <button onClick={changeValue}>{value}</button>
    </>
  );
}

```
![スクリーンショット 2022-12-17 22.25.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/c4d4cd05-06de-7de7-7879-2ee4bc2eed87.png)

上記ソースでは、setStateを2回実行し、その結果を`console.log()`で出力している。
setStateを2回実行しているので、結果は2ずつ増えていくかと思うが、結果は`数値は1ずつ増え`、`consoleは初回は0でその後も１ずつ増えている。`

### ・なぜこのようなことが起きるのか
<br>

この原因は、`setStateの呼び出しは非同期であるから。`
setStateが非同期で実行されているので、velueに値が反映されるのも当然非同期になる。

よって、setStateを実行した際は読み取り変数にはまだ`+1`は反映されておらず、valueには初期値の`0`が代入されている状態となる。結果、setStateは
```react
setCount(0 + 1);
setCount(0 + 1);
```
ということになり、`value`変数に1をセットするという行為を2回続けて実行していることになる。

### ・setStateを呼び出した分値を増やす方法
<br>

実際には呼び出した分値を増やす方法もある。それは`setStateに関数を渡す`方法である。

```react
import "./styles.css";
import { useState } from "react";

export default function App() {
  const [value, setValue] = useState(0);

  const changeValue = () => {
    setValue(value + 1);
    setValue(prev => prev + 1);
  };

  return (
    <>
      <button onClick={changeValue}>{value}</button>
    </>
  );
}
```

このようにsetStateに関数を渡すことで、引数`prev`には、
以前のsetValueでセットした`（count + 1）`がそのままセットされるようになる。

これは、Reactの仕様である。
![スクリーンショット 2022-12-17 22.59.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/649a2cee-4d07-9254-9ab1-57e9d9df8271.png)


## ③オブジェクトをステートする際は、同じ形で設定する。
オブジェクトを管理する際は、更新を実行する際も同じ形で値を設定しなければいけない。

```react
import "./styles.css";
import { useState } from "react";

export default function App() {
  const obj = { name: "sample", age: 22 };

  const [newObj, setNewObj] = useState(obj);

  const changeName = (e) => {
    setNewObj({ name: e.target.value, age: newObj.age });
  };
  const changeAge = (e) => {
    setNewObj({ name: newObj.name, age: e.target.value });
  };

  return (
    <>
      <input type="text" value={newObj.name} onChange={changeName} />
      <input type="number" value={newObj.age} onChange={changeAge} />
      <div>
        <p>Name: {newObj.name}</p>
        <p>Age: {newObj.age}</p>
      </div>
    </>
  );
}

```
![スクリーンショット 2022-12-17 23.20.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/3e8cea17-4b00-f2d4-73cc-6e638bedd70a.png)

ポイントは以下の箇所。
```react
  const obj = { name: "sample", age: 22 };

  const [newObj, setNewObj] = useState(obj);

  const changeName = (e) => {
    setNewObj({ name: e.target.value, age: newObj.age });
  };
  const changeAge = (e) => {
    setNewObj({ name: newObj.name, age: e.target.value });
  };
```

inputに名前もしくは年齢を更新した際に、それぞれに対応する箇所が更新するようになっているが、
更新する際は必ず`初期値で設定したオブジェクトと同じ形で更新を記述しなければならない。`

名前、もしくは年齢だけが更新されるからといって、以下のような記述をするとエラーになる。
```react
const changeName = (e) => {
    setNewObj(name: e.target.value);
  };
  const changeAge = (e) => {
    setNewObj(age: e.target.value);
  };
```

また、以下のように記載した場合、記載されなかったプロパティは消滅する。
```react
  const changeName = (e) => {
    setNewObj({ name: e.target.value });
  };
  const changeAge = (e) => {
    setNewObj({ age: e.target.value });
  };
```

## ④オブジェクトを更新する際は、必ず新規オブジェクトを作成する。
　オブジェクトを更新する際は異なる新しいオブジェクトを作成するという、useState上の制約が存在する。

先ほどのソースのsetStateを一部以下のように書き換えてみる。

```react
const obj = { name: "sample", age: 22 };

const [newObj, setNewObj] = useState(obj);

const changeName = (e) => {
    obj.name = e.target.value;
    setNewObj(obj);
  };
  const changeAge = (e) => {
    setNewObj({ name: newObj.name, age: e.target.value });
  };
```
上記のソースは、inputで入力した値を定義した既存のオブジェクト`obj`のnameプロパティに代入し、setStateに`obj`を設定している。

この結果、changeNameを実行する方のinputタグが変更できなくなる。

先ほどの説明で記載していたソースの、
```react
    setNewObj({ name: e.target.value, age: newObj.age });

    setNewObj({ name: newObj.name, age: e.target.value });
```
という箇所は、`{}`を記載して設定している。
`{}`はオブジェクトの新規作成を意味しており、そのオブジェクトのプロパティに対して新しい値を設定している。

オブジェクトの更新の際は必ず上記のような形で更新を行うこと。


## ⑤Stateの引き継ぎ

ReactのStateは、コンポーネント毎にStateを保持している。
```react
import "./styles.css";
import { useState } from "react";

export default function App() {
  return (
    <>
      <Count title="A"/>
      <Count title="B"/>
    </>
  )
}

export const Count = ({ title }) => {
  const [count, setCount] = useState(0);

  const countUp = () => {
    setCount(prev => prev + 1);
  };

  return (
    <>
      <p>{title}</p>
      <button onClick={countUp}>{count}</button>
    </>
  );
 }
```
上記ソースを実行すると、`A`と`B`は同じコンポーネントから作成しているが、それぞれが独立してStateを保持し、値を更新しているのがわかる。
![スクリーンショット 2022-12-18 0.29.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/f49840f3-4b35-6695-683a-1a19b0f2e79c.png)

これはReactが、
`React要素のツリー内の位置によってどのコンポーネントのStateかを判別している`
からである。

逆にReact要素のツリー内の位置が同じ場合、<font color="red">Stateは引き継ぐという性質を持つ。</font>
以下は、ボタンを押すとコンポーネントが切り替わるという処理を示したソースである。

```react
import "./styles.css";
import { useState } from "react";

export default function App() {
  const [toggle, setToggle] = useState(true);

  const changeToggle = () => {
    return setToggle((prev) => !prev);
  };
  return (
    <>
      <button onClick={changeToggle}>toggle</button>
      {toggle ? <Count title="A" /> : <Count title="B" />}
    </>
  );
}

export const Count = ({ title }) => {
  const [count, setCount] = useState(0);

  const countUp = () => {
    setCount(prev => prev + 1);
  };

  return (
    <>
      <p>{title}</p>
      <button onClick={countUp}>{count}</button>
    </>
  );
};

```
まずAの状態で数値を増やす。
![スクリーンショット 2022-12-18 0.36.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/0cc26385-a4d7-46d9-2ee6-40a2028f0185.png)
その後toggleボタンを押しBに切り替えると、
![スクリーンショット 2022-12-18 0.37.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/d2bb2493-8919-ebf8-de88-4e0bfbf25ff8.png)
Bの値も同じ値になっていることがわかる。これが`Stateの引き継ぎ`である。

### ・同じ要素ツリー内でStateを独立させるには
<br>

同じツリーでそれぞれStateを独立させるには、それぞれのコンポーネントに対し、一意の`Key`を設定する。
先ほどのソースの一部を以下のように変更する。
```react
{toggle ? <Count title="A" key="A" /> : <Count title="B" key="B" />}
```

このようにkeyを設定することで、独立させることが可能になる。

Aの状態でカウントを増やす。
![スクリーンショット 2022-12-18 0.42.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/328db9ec-bd7a-6d03-d75d-916e1e886bbb.png)
その後toggleボタンを押下し、Bに切り替えると、
![スクリーンショット 2022-12-18 0.43.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/430a3565-2241-ec0a-101e-da09d6f7c760.png)
Bが更新されず、独立していることがわかる。

## Stateのリセットと保持
先ほどのStateの引き継ぎで作成したソース実行した際、

**Aのカウントをあげる  →  Bに切り替える  →  またAに戻す**

という動作を行うと、Aのカウントがリセットされ、0に戻っていることがわかる。
これは、切り替わった事で`コンポーネントが消滅し、Stateがリセットされてしまった`からである。

このような場面で、コンポーネントが消滅した後もStateを保持したい時には、

`親コンポーネントでStateを管理し、Propsで子に値を渡す`

という方法をとるのが良い。

```react
import "./styles.css";
import { useState } from "react";

export default function App() {
  const [toggle, setToggle] = useState(true);
  const [countA, setCountA] = useState(0);
  const [countB, setCountB] = useState(0);

  const changeToggle = () => {
    return setToggle((prev) => !prev);
  };
  return (
    <>
      <button onClick={changeToggle}>toggle</button>
      {toggle ? (
        <Count title="A" key="A" count={countA} setCount={setCountA} />
      ) : (
        <Count title="B" key="B" count={countB} setCount={setCountB} />
      )}
    </>
  );
}

export const Count = ({ title, count, setCount }) => {
  const countUp = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <p>{title}</p>
      <button onClick={countUp}>{count}</button>
    </>
  );
};
```
ポイントは以下の箇所。

```react
  const [countA, setCountA] = useState(0);
  const [countB, setCountB] = useState(0);

 <Count title="A" key="A" count={countA} setCount={setCountA} />

 <Count title="B" key="B" count={countB} setCount={setCountB} />
```
AとBそれぞれのStateを定義し、定義した読み取り変数とsetStateをそのままpropsとして渡している。
そして子コンポーネントがpropsを受け取って処理し、結果を反映している。
```react
export const Count = ({ title, count, setCount }) => {
  const countUp = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <p>{title}</p>
      <button onClick={countUp}>{count}</button>
    </>
```

ちなみに、親コンポーネントで定義するStateを１つだけにしてしまうと、
`AとBで同じStateの値をpropsに渡していることになる`ので、切り替えても同じカウント数になってしまう。
必ずそれぞれのStateを定義すること。

```react
// NG
  const [count, setCount] = useState(0);

 <Count title="A" key="A" count={count} setCount={setCount} />

 <Count title="B" key="B" count={count} setCount={setCount} />
```

# 参考文献

https://www.udemy.com/course/react-complete-guide/