useStateを使う上での注意点について解説していきます!useStateの基礎的な内容を知りたい方はuseStateの基本を理解しようをご覧ください👀
useStateの注意点
useStateを呼び出す場所に気を付ける
React Hooksは、
- 関数コンポーネント・カスタムフック内
- トップレベルのみ(ループや条件分岐・ネストされた関数内で呼び出してはいけない)
でしか呼び出すことができません。このルールを守らないとstateを正しく保持できなくなります。
[公式 : フックのルール(https://ja.reactjs.org/docs/hooks-rules.html)より]
例を踏まえて説明していきます。以下の例ではpropsで渡された値の真偽によってuseStateの呼び出しが変わります。つまりトップレベルで呼び出されていないことがわかります。
const UseStateAnti: React.FC<boolean> = (isEnabled) => {
if (isEnabled) {
const [count, setCount] = useState(0);
}
...
このコードはバグを起こす可能性があります。なぜなら、Reactはフックが呼ばれる順番に依存しているからです。
フックの呼び出される順番がレンダリング毎に変化しない場合は、それぞれのフックに対してstateを割り当てることができますが、条件分岐などでフックを呼び出してしまうと、フックの順番が変化してしまいます。そうなるとReactはフックに何を割り当てるかわからなくなり、バグを引き起こす原因となります。
useState(0)
useState("hoge")
useEffect(updateTitle)
useState("hoge")
useEffect(updateTitle)
このようなことが起きないように、Hooksのルールを強制できるeslint-plugin-react-hooksプラグインがあるので、使用することを推奨します。
破壊的変更をしない
以下の例のような、input要素の入力された値を配列のstateで管理するような関数コンポーネントについて考えます。
import React, { useState } from "react";
const UseStateAnti2: React.FC = () => {
const [messages, setMessages] = useState<string[]>([]);
const [message, setMessage] = useState("");
const handleOnClick = () => {
messages.push(message);
setMessages(messages);
};
return (
<>
<h4>pushed list</h4>
<ul>
{messages.map((m, i) => (
<li key={i}>{m}</li>
))}
</ul>
<input value={message} onChange={(e) => setMessage(e.target.value)} />
<button onClick={handleOnClick}>追加</button>
</>
);
};
input要素に入力した値が変化する毎にstateであるmessage
が変更され、追加ボタンがクリックされるとmessage
を配列として管理するstateのmessages
にmessage
をpushしています。
しかしこのコードでは追加ボタンを押すと以下のような結果となります。

ご覧の通り、ボタンを押してもstateであるmessages
が変化していないことがわかります。この原因は、
messages.push(message);
にあり、Reactはstateの変更を検知して再レンダリングを行うのですが、そのstateの変更の判定にObject.is()
を使用しています。(Object.is()についてはこちらを参照)
そのため同じ配列のstateで更新しても、変更は検知されず画面の再レンダリングもされないということです。
解決策
新しい配列を生成してstateを更新しましょう。具体的には、例を以下のように変更してください。
setMessages([...messages, message]);
このように修正すればstateを更新することができるようになります。

useStateで更新したstateは即座に更新されるわけではない
最後に注意すべき点としては、useStateで更新したstateは即座に更新されるわけではないということです。以下の例を見てみましょう。
import React, { useState } from "react";
const UseStateAnti3: React.FC = () => {
const [count, setCount] = useState(0);
const handleOnClick = () => {
setCount(count + 1);
setCount(count + 1);
console.log(count);
};
return (
<>
<h3>count : {count}</h3>
<button onClick={handleOnClick}>Click me</button>
</>
);
};

こちらボタンをクリックする度にstateであるcount
を+2しようとしていますが、setCount
直後のcount
を見てみると、更新する前の値が出力されていることがわかるかと思います。さらに、setCount
は2回呼び出されているのですが+1しかcount
が更新されていません。
これは、setStateの呼び出しが非同期であるためです。count
は2回めのsetCount
時に値が更新されておらず、console.log
呼び出し時にもそのままの値であることがわかります。
解決策
setState
にコールバック関数を用いると更新直前のstateを必ず参照できるので、更新直後のstateに依存する場合はこちらを使うとよいでしょう。
const handleOnClick = () => {
setCount((c) => c + 1);
setCount((c) => c + 1);
};
以下のように新しい変数に更新後の値を格納してしまえば参照することができます。
const handleOnClick = () => {
const clickCount = count + 1;
setCount(clickCount);
console.log(clickCount);
};
まとめ
今回、useStateを使う際の注意点として、
- 関数コンポーネント・カスタムフック内、トップレベルのみで呼び出す
- stateは破壊的変更をせず、新しいstateで更新する
- useStateで更新したstateは即座に更新されるわけではないため、更新直後のstateに依存する処理を行う場合は、関数を用いた更新をする
について説明しました!
この記事を参考に是非useStateを使いこなしてください🙌