状態管理を行うuseStateは、ReactでUIを開発する際、特によく使用するフックです。

useStateを使えるようになれば、以下のようなアプリも作れるようになります。


useStateで作ったタスク管理アプリ


本記事で、useStateの基本的な使い方をマスターしておきましょう。


1.useStateとは

useStateは、コンポーネント内で、「state」を管理するためのフックです。


useStateの例

const[value, setValue]=useState(initialValue);


「state」は、そのコンポーネントが持つメモリ(記憶)です。

また「state」は、普通の変数のように「関数が終わると消えてしまう」ようなものではありません。

変更されると再レンダリング(描画)されるようなデータを保持します。

つまりは、useStateを使うことで、「値を保持しつつ画面を再描画」「値の変更をトリガーに再描画」させられます。


2.useStateの基本構文

useStateの構文は以下の通りです。


useStateの構文例

const[value, setValue]=useState(initialValue);

useStateの各要素の意味

const[現在の状態, 状態を更新するset関数]=useState(初期値);


なお、実際のコーディングでは、慣習として

[~~~~(小文字から始まる英単語),~~~~set(大文字から始まる英単語)] 

というふうにuseStateの返り値を命名します。


2-1.引数:初期値

以下の例文の「initialValue」に当たる要素が、useStateで管理するstateの初期値です。

const[value, setValue]=useState(initialValue);

stateの初期値には、どんな型の値でも設定できますが、初回レンダー後は、使われません。


また、以下のように、関数(初期化関数:initializer functionと呼びます。)を渡すこともできます。

関数を渡す場合、コンポーネントを初期化するときに初期化関数が呼び出され、その返り値を初期値として保持します。

const[value, setValue]=useState(createInitialValue());


なお、初期化関数は、



である必要があります。


初期化関数の落とし穴

以下の初期化関数は、同じように動作するように見えます。

しかし実は、以下のように、動作が異なります。


例1(関数を呼び出した結果を渡しており、レンダー毎に関数が実行される)

const[value, setValue]=useState(createInitialValue());

例2(関数自体を渡しており、初回レンダー時にだけ関数が実行される)

const[value, setValue]=useState(createInitialValue);


例1では、初期化が重い処理だとパフォーマンスが悪くなるので注意が必要です。


2-2.返り値1:現在の状態

現在のstateが格納されています。

初回レンダー中は、初期値(上記ではinitialValueに当たる要素)と同じ値になります。


2-3.返り値2:状態を更新するset関数

以下の例文の「setValue」に当たる要素が、状態を更新するset関数です。

set関数を使うことで、stateの値を更新し、再描画を引き起こせます。

const[value, setValue]=useState(initialValue);


set関数は、以下のように使います。

setValue(nextValue);


引数であるnextValueは、次にstateに持たせたい値で、いずれの型の値でも入れられます。

set関数の使い方としては、

setValue(5);

のように、「次のstateを直接渡す方法」があります。


また、以下のように、nextValueに関数(更新用関数:updater functionと呼びます。)を渡すことも可能です。

setValue(prev=> prev +1);


ただし、更新用関数は、



である必要があります。


なお、set関数の更新が実行されるのは、再描画が起きた後です。

そのため、set関数を呼び出した直後にstateを読み取っても、新しい値は表示されません

また、現在のstateと新しいstateの値が同じ場合、描画がスキップされます。


3.useStateで配列やオブジェクトを扱う方法

useStateでは、以下のように、stateに配列やオブジェクトを持たせることが可能です。

const[items, setItems]=useState(["a","b"]);
const[user, setUser]=useState({name:"Tarou",age:50});


ただし、useStateで配列やオブジェクトを扱う場合、注意したい点があります。


stateに格納される配列やオブジェクトは、基本イミュータブル(immutabl:書き換え不能)です。

useStateに持たせた配列やオブジェクトを操作する場合は、元の配列やオブジェクトを直接操作するのではなく、新しい配列やオブジェクトを作成(コピー)して、そのデータをstateにセットする必要があります。


useStateで配列を扱う方法


useStateでオブジェクトを扱う方法


useStateでオブジェクト配列を扱う方法


4.実際にuseStateを使ってみよう(メモアプリ)

ここでは、以下のような「メモアプリ」を使って、useStateの使い方や仕組みを理解しましょう。



このメモアプリは、useStateを使って「追加・削除・編集」という基本的なCRUD操作を実装しました。

配列の追加にはスプレッド構文、削除にはfilter、更新(編集)にはmapを使うことで、React の「状態を直接変更せず、新しい配列を作る」という原則を学べます。

また、編集モードの切り替えには三項演算子を使っており、条件付きレンダリングによって、UIを切り替える仕組みも理解できます。

ソースコード


4-1.useStateで管理している状態

このメモアプリで管理している状態は以下の通りです。

const[text, setText]=useState('');//入力欄に入力している文字。 
const[memos, setMemos]=useState([]);//メモの一覧。
const[editingIndex, setEditingIndex]=useState(null);//編集中のメモのindex。
const[editingText, setEditingText]=useState('');//編集用入力欄に入力している文字。


4つのuseStateで、以下の4つの機能を実装しています。



4-2.メモの追加

以下は、メモアプリの「追加機能」部分だけ抜き出したものです。


上記のdeleteMemo関数では、


  1. trim()で未入力時のメモ登録の防止
  2. スプレッド構文を使って配列をsetMemosで追加
  3. メモ追加後に入力欄をクリア


といった内容を行っています。


4-3.メモの削除

以下は、メモアプリの「削除機能」部分だけ抜き出したものです。


上記のaddMemo関数では、filterを使って、削除したいindex以外を残した新しい配列を作って、setMemosで上書きしています。


4-4.編集モードの切り替え

以下は、メモアプリの「編集モードの切り替え」部分だけ抜き出したものです。


上記のstartEdit関数では、

  1. 編集対象の index を保存
  2. 編集用の入力欄に、元のメモ内容をセット

といった内容を行っています。


また、JSX部分では、

{editingIndex === index ?(<>編集UI</>):(<>通常時のUI</>)}

という三項演算子を使って、UIの切り替えを行っています。


三項演算子とは

三項演算子は、JSXでよく使われるJavaScriptの演算子で、以下のような構文で使用されます。

(条件式)?(条件式が真なら評価される):(条件式が偽なら評価される) 


今回は、stateの値を条件として、2種類のHTMLを返す使い方をしました。


4-5.編集内容の保存と取り消し

以下は、メモアプリの「編集内容の保存と取り消し」部分だけ抜き出したものです。


上記のsaveEdit関数では、


  1. mapを使って、目的のindexのメモだけ書き換える
  2. 編集が終わったらeditingIndexをnullに戻す
  3. 編集用入力欄をクリアにする


といった内容を行っています。

editingIndexをnullに戻すことで、先ほど解説した三項演算子によって、編集UIから通常時のUIに切り替わります。


なお、上記のcancelEdit関数は、saveEdit関数の「mapを使って、目的のindexのメモだけ書き換える」という操作がないだけで、ほかは同じ動作を行います。


5.注意点

5-1.useStateの呼び出し位置

useStateは、「コンポーネントのトップレベル」か「カスタムフック内」でのみ使えます。

そのため、ループや条件文のなかに記述すると、エラーが発生するため注意が必要です。

5-2.イベントハンドラの記述方法

「set関数自体」あるいは「useStateを使った関数」を、間違った方法でイベントハンドラに渡すと、

Too many re-renders. React limits the number of renders to prevent an infinite loop. 

というエラーが出ることがあります。

これは、レンダー中にset関数を呼び出し、コンポーネントが無限ループに入ると起こります。


イベントハンドラの正しい渡し方


5-3.useStateの更新タイミング

Reactにおいて、stateの更新は、バッチ処理で行われます。

つまり、useStateのset関数は、呼び出されたその場で値を書き換えるわけではありません。

set関数は、呼び出されると、処理をキュー(次の再描画でまとめて更新するための予約の列のようなもの)」に登録します。

そのため、set関数の直後にstateを読んでも再描画が起きていないため、まだ古い値が表示されます。


よって、useStateのバッチ処理について理解していないと、以下のような勘違いをする場合があります。


5-4.開発環境での振る舞い

開発環境(npm run devなど)でReactの動作を確認する場合、「初期化関数」「更新用関数」が2回呼び出されますが、これは正常な動作です。

本番環境では、2回呼び出されません。

6.あとがき

useStateは状態を管理するためのフックで、UIの変化は基本的にuseStateで制御します。

そのため、React開発において、useStateは非常に重要なフックです。

特に、useStateは別のフックと組み合わせて使うこともあるので、本記事で基本的な使い方をきちんとマスターしておきましょう。