useStateは必要最低限が良い

概要

必要不可欠でない state 変数をすべて削除するという記事を読んで覚えておきたいと思ったのでブログに書こうと思います。
そもそもuseStateは、UIを操作するための状態管理の仕組みです。
例えば、フォームが送信中であることや、入力エラーがあることを表現するために、以下のコードを用意します。

const [isSubmitting, setIsSubmitting] = useState(false)
const [isError, setIsError] = useState(false)

フォーム送信のtry catch文があったとしたら、以下のようなsetterになると思います(雰囲気を掴む程度のコードです)。

    try {
      setIsSubmitting(true);
      await submit(value);
    } catch (err) {
      setIsError(true);
    } finally {
      setIsSubmitting(false);
    }

なお完全な余談ですが、useStateに限らず予期しないエラーや考慮不足によって延々と送信中でローディングがグルグルする処理を見たのは1度や2度ではないので、注意したいですね。自分も新人の頃はやった記憶が、、。

useStateの注意点

useStateの注意点としては、やはり状態管理はシンプルに保つ点だと思います。
複雑な状態管理は、意識しないといけない状態が多くあり、状態ごとに矛盾が生じないようにしなければなりません。
そのため、見通しも悪くバグが生じやすいコードになってしまうと考えられます。

不必要なuseStateを削除する

必要不可欠でない state 変数をすべて削除するの記事では、stateを減らす考え方について解説されているので紹介します。
元々、7つのstateがありました。これを不必要なものを削っていきます。

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);

この state で矛盾は生じないか?

以下のように解説されています。

例えば、isTyping と isSubmitting の両方が true となることはありえません。矛盾がある state とは通常、state の制約が十分でないことを意味します。2 つのブール値の組み合わせは 4 通りありますが、有効な状態に対応するのは 3 つだけです。このような「ありえない」state を削除するためには、これらをまとめて、typing、submitting、または success の 3 つの値のうちどれかでなければならない status という 1 つの state にすればよいでしょう。

それぞれの状態を矛盾なく管理しなければいけない状況を避けて、かつstatusの状態だけ見れば良いようにしています。

同じ情報が別の state 変数から入手できないか?

以下のように解説されています。

もうひとつの矛盾の原因は、isEmpty と isTyping が同時に true にならないことです。これらを別々の state 変数にすることで、同期がとれなくなり、バグが発生する危険性があります。幸い、isEmpty を削除して、代わりに answer.length === 0 をチェックすることができます。

stateで管理せずとも分かることは、わざわざstateにしないようにしています。

別の state 変数の逆を取って同じ情報を得られないか?

以下のように解説されています。

isError は不要です。なぜなら代わりに error !== null をチェックできるからです。

errorのstateからエラーかどうか分かるため、isErrorは不必要ですね。

結果

不必要なstateを削って、7つから3つに減りました。

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'

とりあえずstateに入れておくのは手軽ではありますが、結局後々管理が辛くなりがちなので、必要な分だけ管理する思考は大切ですね。