Reactの入門者がReact Hookを学び始めた時に最初に理解しなければならないのがuseStateです。useStateを使うとFunctionalコンポーネント(関数コンポーネント)内で状態管理を行うことができます。状態管理と聞くとイメージが湧きにくいですが要はコンポーネントの中で変数を扱うことができる機能です。例えばinput要素に入力した文字をコンポーネント内で保持したい場合にuseStateを利用することができます。

useStateはコンポーネントの中で変数の値を保持、更新ができることから使用頻度の高い機能なので簡単なサンプルを使って動作確認を行なっていきます。

useStateを使いこなさなければFunctionalコンポーネントでアプリケーションを作成することはできません。

シンプルな例を通してuseStateの理解

useStateの設定

useStateを利用するためには、useStateをimportする必要があります。


import React, { useState } from 'react';

コンポーネント内で状態管理を行いたい変数をuseStateを使って宣言します。初めて使用する人であれば最初は書式に違和感のある方も多いもしれませんが使えばすぐになれるものです。まず最初に下記のuseStateの書式をしっかりと覚えてください。下記の記述例の場合はcountが変数でsetCountメソッドを使ってcountの値を変更することができます。useStateの()で設定している0がcount変数の初期値です。


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

上記の設定を行うだけでコンポーネント内に{ count }を追加するとcountの現在値を表示することができます。初期値は0に設定し、そこから何も変更を行っていないためブラウザ上には現在値である0が表示されます。


import React, { useState } from 'react';

function Count() {

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

    return (
        <div>
            <h1>Counter</h1>
            <h2>カウント: { count }</h2>
        </div>
  );
}

export default Count;
countの値を表示
countの値を表示

countの値の更新

先程setCountメソッドでcountの値を更新できると説明しました。どのように変更するのか実際にコードを使って確認していきます。

クリックしたらcountの値が更新できるようにボタンを追加し、onClickイベントを追加します。onClickの中でsetCountメソッドを使ってcountの値を更新します。


<button onClick={() => setCount(count + 1)}>+</button>

countを1増やすだけではなく-1に減らすボタンも追加します。


import React, { useState } from 'react';

function Count() {

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

  return (
      <div>
          <h1>Counter</h1>
          <h2>カウント: { count }</h2>
          <button onClick={() => setCount(count + 1)}>+</button>
          <button onClick={() => setCount(count - 1)}>-</button>
      </div>
  );
}

export default Count;

+ボタンをクリックすると1増え、-ボタンをクリックすると1減るカウンターが作成できました。ボタンを押すとコンポーネントの内で行ったカウントの増減が即座にブラウザ上にも反映されます。

クリックでCountの値が増減
クリックでCountの値が増減
コンポーネントのsetCountで更新したらブラウザ上にも反映されるのが不思議だと感じる人もいるかもしれません。即座に反映されるのはuseStateで変数の値を更新する度にコンポーネントが再実行され、ブラウザ上で変数の再描写が行われているためです。ReactではuseStateによる変数の更新だけではなくpropsが更新されてもコンポーネントの再描写が行われるということを理解しておく必要があります。再描写を行う仕組みがない場合はstateを更新しても最新の情報がブラウザに表示されることはありません。React HooksのuseCallback, useMemo, useRefやmemo等の機能がコンポーネントの再描写に関係しています。

onClickで実行する関数を外側に定義しても動作は変わりません。このようにuseStateを利用するとFunctionalコンポーネント内で値を保持したり、更新したりすることができます。


import React, { useState } from 'react';

function Count() {

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

    const increment = () => setCount(count + 1)
    const decrement = () => setCount(count - 1)

    return (
        <div>
            <h1>Counter</h1>
            <h2>カウント: { count }</h2>
            <button onClick={increment}>+</button>
            <button onClick={decrement}>-</button>
        </div>
  );
}

export default Count;

更新は非同期

increment関数を使うことでcountの数を1増やすことは可能です。increment関数の中で増やしたcountで何か処理をしようとしても更新は非同期なため更新したcountを取得することはできません。


const increment = () => {
  setCount(count + 1);
  console.log(count);
};

+ボタンを押すとコンソールには1増えたcountが表示されると思いますが実際にはcountは即座に更新されていないためボタンを押しても数は増えません。

下記のように+ボタンを押すとブラウザ上のカウントは1増えますがコンソールログのcountは0のままです。

countは即座に更新されない
countは即座に更新されない

countがsetCountで再描写が行われた後にはcountは更新されていることが確認できます。再描写されたことがわかるようにコードを追加します。


import React, { useState } from 'react';

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

  console.log('再描写');
  console.log(count);

  const increment = () => {
    setCount(count + 1);
    console.log(count);
  };
  const decrement = () => setCount(count - 1);

  return (
    <div>
      <h1>Counter</h1>
      <h2>カウント: {count}</h2>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

export default Count;

ページを開くと再描写ではありませんが初めてコンポーネントが実行されるのでコンソールには再描写と0が表示されます。

最初の描写
最初の描写

ボタンをクリックします。incremant関数内ではcountは更新されていませんが再描写した後はcountが更新されていることが確認できます。

再描写後はcount更新
再描写後はcount更新

useStateでbooleanを使う

状態管理を行う変数の値にbooleanを使用することができます。booleanのtrueとfalseを利用することでコンポーネントの表示・非表示を切り替える方法はReactアプリケーションのさまざまな場所で利用されています。

ONボタンとOFFボタンで電源が切り替わるPowerコンポーネントを作成します。


import React, { useState } from 'react';

function Power() {

    const [power, setPower] = useState(false);

    return (
        <div>
            <h1>電源 { power ? 'ON' : 'OFF'} </h1>
            <button onClick={() => setPower(true)}>ON</button>
            <button onClick={() => setPower(false)}>OFF</button>
        </div>
  );
}

export default Power;

useStateで初期値をtrueまたはfalseに設定することができます。上記ではuseStateでfalseに設定しているのでpowerの初期値はfalseになります。

ONボタンとOFFボタンを用意しonClickイベントを使ってpowerの値をtrueまたはfalseに変更できるように設定します。

ブラウザで確認すると下記のように表示され、ONボタンを押すと電源ONと表示され、OFFボタンを押すと電源OFFと表示されます。

電源のON, OFFを切り替え
電源のON, OFFを切り替え

前の値を利用して切り替える

ONとOFFを切り替えるのに2つのボタンを用意していましたが、useStateでは現在の値を元にして新しい値を設定することができます。現在の値を利用したい場合には関数を利用します。prevStateがボタンをクリックする前の値でボタンをクリックするとprevStateの値で別の値に変わります。つまり現在の値がtrueであればfalse、falseであればtrueに変わります。


<button onClick={() => setPower(prevState => !prevState)}>ON/OFF</button>

prevStateとしていますが、名前は任意なのでどのような名前でもつけることが可能です。

ボタンを1つにする
ボタンを1つにする

ボタンをクリックするたびにONとOFFが切り替わります。コンポーネントの表示・非表示を行うToggle機能もこのようにuseStateの値を利用して実装することができます。

Vue.jsを学習してきた人であればbooleanによる条件分岐にはv-ifディレクティブ, v-showディレクティブを利用するかと思います。Reactでは条件分岐にはJavaScriptの機能を利用します。Reactでは条件分岐に独自の機能はありません。

useStateを利用してフォーム

useStateを利用するのは入力フォームを利用した時が多いかと思います。シンプルなフォームを作成を通してさらにuseStateの理解を深めていきましょう。

公式ドキュメントのuseStateのNoteの下記の画像の説明の意味がわからない場合はこれから行っていく説明が理解の助けになるかと思います。

useStateのNote
useStateのNote

メールアドレスとパスワードの入力項目を備えたフォームを作成します。emailとpasswordはuseStateを利用して入力の値を取得します。input要素からはイベントを利用して入力した値を取得しています。


import { useState } from 'react';

function App() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const handleSend = (e) => {
    e.preventDefault();
    console.log(email, password);
  };
  return (
    <div style={{ textAlign: 'center', marginTop: '2em' }}>
      <h1>useStateでフォーム</h1>
      <form onSubmit={handleSend}>
        <div>
          <label>
            メールアドレス:
            <input
              name="email"
              type="email"
              onChange={(e) => setEmail(e.target.value)}
            />
          </label>
        </div>
        <div>
          <label>
            パスワード:
            <input
              name="password"
              type="password"
              onChange={(e) => setPassword(e.target.value)}
            />
          </label>
        </div>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default App;

ブラウザで確認するとメールアドレスとパスワードにの入力を行いSubmitボタンを押すと入力した情報が表示されます。

入力フォームを表示
入力フォームを表示

これまでのuseStateの使い方が理解できていれば難しいところはないかと思います。

次にemailとpasswordを別々の変数にわけて設定を行うのではなくformオブジェクトをuseStateを利用して設定します。先程までの方法ではemail、password以外に入力項目が増えてたら増えた分だけuseStateで新たに変数を定義する必要がありますが下記のようにformオブジェクトを利用した場合は初期値の中に追加した項目のプロパティを追加する必要があります。


const [form, setForm] = useState({ email: '', password: '' });

ではオブジェクトを利用した場合はどのようにformオブジェクトの値を更新することができるのでしょうか。

input要素にはname属性を設定していたのでname属性を利用してどちらの入力項目が入力されたかを確認することができます。


[e.target.name]: e.target.value

formオブジェクトを更新したい場合には更新前のprevStateとspread operatorsを利用して下記のように行うことができます。


setForm((prevState) => {
  return {
    ...prevState,
    [e.target.name]: e.target.value,
  };
});

formオブジェクトを利用すると全体のコードを下記のように変更となります。onChageイベントはname属性でどの項目が入力されたか判定できるため同じhandleChage関数を設定しています。


import { useState } from 'react';

function App() {
  const handleSend = (e) => {
    e.preventDefault();
    console.log(form);
  };
  const [form, setForm] = useState({ email: '', password: '' });
  const handleChange = (e) => {
    setForm((prevState) => {
      return {
        ...prevState,
        [e.target.name]: e.target.value,
      };
    });
  };
  return (
    <div style={{ textAlign: 'center', marginTop: '2em' }}>
      <h1>useStateでフォーム</h1>
      <form onSubmit={handleSend}>
        <div>
          <label>
            メールアドレス:
            <input
              name="email"
              type="email"
              onChange={handleChange}
            />
          </label>
        </div>
        <div>
          <label>
            パスワード:
            <input
              name="password"
              type="password"
              onChange={handleChange}
            />
          </label>
        </div>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default App;

ブラウザで動作確認をしてもemailとpasswordで変数を別々に定義した時と結果は同じです。

setFormのコードについては下記のようによりシンプルに記述することができます。


setForm({
  ...form,
  [e.target.name]: e.target.value,
});

またObject.assignを利用することもできます。


setForm(Object.assign(form, { [e.target.name]: e.target.value }));

ここまでの説明が理解できればuseStateを使いこなすことが可能です。