ずっと気になってたuseStateの更新用関数の使い道について考える

## はじめに
Reactを学習し始めたころ、`useState`のset関数には更新用関数を設定することができると学びました。

しかし、今のところ業務でそれらしきコードを見たことがありません。
また、公式サイトで示されている例を見ても、あまりその実用性がピンときていません。

そこで、どんな場面で使えるのかを考えてみます。

## 更新用関数とは
次のような場面を考えてみます。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/b98ae754-ff29-2c4b-c82a-46578e7c2fd6.png'>


コードは以下の通りです。
```react:App.tsx
import { useState } from "react";

const App = () => {
  const [state, setState] = useState(0);
  const handleClick = () => {
    setState(state + 1);
    setState(state + 1);
    setState(state + 1);
  };
  return (
    <>
      <div>{state}</div>
      <button onClick={handleClick}>count up</button>
    </>
  );
};

export default App;
```
`setState`を3回呼び出して、`state`を3回更新しています。
初期値が0なので、1回のボタンクリックで3ずつ増える想定です。

しかしボタンを押すとわかりますが、実際には1しか増えません。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/f29aacd6-87f6-f764-9c1f-6314979df6ea.png'>

これは、`setState`を実行した時点ではまだ`state`は更新されていないためです。
更新前の`state`を参照しているので、何度`setState`を呼び出しても初期値の0に対して1を追加する、という処理を繰り返しているだけです。

このような挙動を回避するため、`setState`にわたすのが**更新用関数**です。

以下のように`setState`に関数を渡すことで、参照する`state`が更新後の`state`であることを保証してくれます。

```react:handleClickのみ抜粋
  const handleClick = () => {
    setState((prevState) => prevState + 1);
    setState((prevState) => prevState + 1);
    setState((prevState) => prevState + 1);
  };
```

更新用関数は引数に直前の`state`を受け取ります。
変数名は自由ですが、一般的にはstateの変数の1文字目、または頭に`prev`とつけるそうです。

結果は以下のようになります。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/5c572dcf-67db-6224-926d-6eee9b1add13.png'>


というのが更新用関数の説明です。

## 更新用関数、使うか？
さて、更新用関数が何であるかを改めて確認しましたが、これって使うんでしょうか。
上記で紹介したようなコードは実際のお仕事ではまず見かけません。
基本的には更新したい値をそのままset関数に指定すれば、それでオッケーな気がします。

よって、あまり使い道はないように思っています。
実際、公式サイトでも次のように言っています。

> ほとんどのケースでは、どちらのアプローチでも違いはありません。

https://ja.react.dev/reference/react/useState#is-using-an-updater-always-preferred

ということなので、基本的にはあまり意識しなくてもよさそうです。
使う場面としては以下のような場面とのこと。

> 一方で、同じイベント内で複数回の更新を行う場合、更新用関数が役に立ちます。また、state 変数自身を参照することが難しいケースにも有用です

ただ、これってどういう場面なんでしょう
ChatGPTの力も借りながら、ちょっと考えてみます。

## 同じイベント内で複数回の更新を行う
考えられるのは、「複数のチェックボックスをまとめてチェックする」でしょうか。

以下はコード例です。
※TypeScriptで書いています。
```react:App.tsx
import React, { useState } from "react";

interface ListItem {
  id: number;
  text: string;
  checked: boolean;
}

const App: React.FC = () => {
  const [items, setItems] = useState<ListItem[]>([
    { id: 1, text: "アイテム 1", checked: false },
    { id: 2, text: "アイテム 2", checked: false },
    { id: 3, text: "アイテム 3", checked: false },
  ]);

  const checkAllItems = () => {
    setItems((prevItems) =>
      prevItems.map((item) => ({ ...item, checked: true }))
    );
  };

  const handleChange = (id: number) => {
    setItems((prevItems) =>
      prevItems.map((item) =>
        item.id === id ? { ...item, checked: !item.checked } : item
      )
    );
  };

  return (
    <div>
      <button onClick={checkAllItems}>まとめてチェック</button>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            <label>
              <input
                type="checkbox"
                checked={item.checked}
                onChange={() => handleChange(item.id)}
              />
              {item.text}
            </label>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;
```

画面としては以下のようになります。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/36a38c2e-d6bc-3038-184e-fa2a52997f6b.png'>

と、書いていて気づきましたが、これ実際は複数回の更新ではなさそうですね。
set関数を呼んでいるのは一度だけなので、ちょっと違いそうです。

ただ、ポイントとしては`checkAllItems`と`handleChange`ともに更新用関数を使用して値を更新しているという点です。

これにより、直前がどのような状態であっても、更新後の`state`を使って確実に処理をしてくれるので、とくにまとめてチェックの場合にはおかしな結果とならずに済みそうです。

## state変数自身を参照することが難しい場合
これはちょっと私の文章読解能力では理解しきれなかったのですが、自分自身に対して繰り返し処理をするものだと理解すると、例えば経過時間を表示するタイマーとかが考えられるかもしれません（あまり実用的ではないですが）

以下は間違ったコード例です
```react:App.tsx
import { useState, useEffect } from "react";

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1);
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, []);

  return <div>{count}秒経過</div>;
}

export default App;
```
これを画面に表示させるとわかりますが、ずっと1秒のままです。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/075f3033-c289-c95d-513e-7929ed1f7ed9.png'>

これを、更新用関数を設定すると以下のようになります。
```react:timer関数のみ抜粋
    const timer = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);
```

更新用関数によって、更新後の値であることが保証されるので以下のように秒数が増えていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/d3af0ab7-f2e3-0edc-6690-d45780fd9603.png)


## まとめ
なんとか更新用関数の使い道を探ろう、と意気込んでChatGPTやClaudeAIにも聞いて色々考えてみましたが、正直な話絶対に更新用関数を使わなければならない場面というのはそうそうないように思いました。

最後のタイマーは確かに更新用関数がないと難しいかもしれませんが、これが現実的に生きてくる場面はあまりない気がしました。

なんとなく、徒労に終わった感じも否めないですが、今回は以上です。