useStateを理解するためにサンプルを作って確認しました【React/useState】

JavaScript

ReactのuseStateがいまいち理解できなかったので、サンプルをいくつか作成して動きを確認しました。主にリアルタイムの動きに使えそうです。

useStateとは

Reactにはコンポーネントに状態を保管、更新できるuseStateという仕組みがあります。
構文は次の通りです。※使用するにはuseStateをインポートしておきます。

import REACT, { useState } from "react"; //useStateをインポート

export const App = () => {
  const [<保管する値>, <値を更新する関数()>] = useState(<初期値>);
  値を更新する関数(<どんなふうに値を更新するか>)
}

日本語だと更にわからなくなりました、、。
実際は次のように使います。countがAppコンポーネントに保管する値、setCount()でcountの値を1つ増やしています。

import REACT, { useState } from "react";

export const App = () => {
  const [count, setCount] = React.useState(0);  // countの初期値は0
  const onClickCountUp = () => {
     setCount(() => count + 1); // countの値を1つ増やす
  }
  return(
    <div className="container">
         <button onClick={onClickCountUp}>カウントアップ</button>
         <p>{count}<p> //カウントアップされた値が表示される
    </div>
  )
}

値を更新する関数名は、保管するcountの先頭にSetをつけた名前にするのが通例のようです。
ここではsetCountになります。

メール入力の確認をリアルタイムで表示

実際に使えそうなシチュエーションを考えてサンプルを作成しました。
こちらはメールアドレスを入力したら、確認用として同じテキストが下に表示されます。
ECサイトの申し込みフォームにありそうなやつです。

実際の動きはこちらで確認ください!

See the Pen react-form-usestate by donguri2020 (@m-ke) on CodePen.

コード全体

こちらがコード全体です。useStateに関する箇所を色付けしました。

import REACT, { useState } from "react"; 

const ContentArea = () => {
  const [emailTxt, SetEmailTxt] = useState(""); 
  const onChangeEmail = (e) => {
    const targetValue = e.target.value;
    
    // 確認用テキスト表示
    SetEmailTxt(targetValue);
  }

  const onClickForm = ()=> {
      alert("サンプルフォームです。送信されません");
  }

  return(
    <div className="container">
      <section>
        <p className="item_name">メールアドレス<span className="require">必須</span></p>
        <input type="text"  onChange={onChangeEmail} />
        <div className="confirmation">
          <p>確認:</p>
          <p className="confirmation_txt">{emailTxt}</p>
        </div>
        
        <button onClick={onClickForm} className="submit_btn">
          登録
        </button>
      </section>
    </div>
  )
}
ReactDOM.render(
    <ContentArea />,
  document.getElementById('root')
);

useStateでテキストがリアルタイムで反映される仕組み

useStateの設定は次の通りです。

const [emailTxt, SetEmailTxt] = useState(""); 

emailTxtにフォームに入力した値を格納し、SetEmailTxtで入力した値を更新します。
初期値は””で空文字にします。

フォームの値を変更したら、関数onChangeEmailが実行されます。

<input type="text" onChange={onChangeEmail} />

onChangeEmail関数では、フォームに入力した値(ここではtargetValue )をSetEmailTxt()に渡すことでemailTxtの値を更新しています。

  const onChangeEmail = (e) => {
    const targetValue = e.target.value;
    
    // 確認用テキスト表示
    SetEmailTxt(targetValue);
  }

SetEmailTxt()で更新したemailTxtは、次のように{}で囲むとHTMLの中でも変数の中身を展開できます。

<p className="confirmation_txt">{emailTxt}</p>

これでフォームを入力する度に、同じテキストが表示できる動きが完成しました!

スイッチのオン・オフの動きを作成

useStateの仕組みを使ってオン・オフのスイッチを作成しました。
スイッチのオン・オフで画像、CSSなど複数の要素が変更されますが、useStateを使うと簡単に実装できます。

実際の動きはこちらをご確認ください!

See the Pen react-onoff by donguri2020 (@m-ke) on CodePen.

コード全体

こちらがコード全体です。useStateに関する箇所を色付けしました。

import REACT, { useState } from "react"; 

const ContentArea = () => {
 const [switchBtn, setSwitchBtn] = useState(false);

 return(
   <div className="container">
     <div className="switch_wrap">
       <img onClick={() => setSwitchBtn(!switchBtn)} src={`https://m-kenomemo.com/sample/react-usestage/images/switch-${switchBtn ? "on" : "off"}.png`} />
     </div>
     
     <div className={`light_wrap ${switchBtn ? "on" : ""}`}>
       <img src={`https://m-kenomemo.com/sample/react-usestage/images/light-${switchBtn ? "on" : "off"}.png`} />
     </div>
   </div>
  )
}
ReactDOM.render(
    <ContentArea />,
  document.getElementById('root')
);

useStateで変更した値を参照して、画像やCSSを切り替える

useStateの初期値はboolean型のfalseにします。
falseとtrueに切り替えることでオン・オフの動きを実装します。

const [switchBtn, setSwitchBtn] = useState(false);

useStateの値でスイッチの画像を変更する

クリックしたらsetSwitchBtn()switchBtnの値を変更します。

<img onClick={() => setSwitchBtn(!switchBtn)} src="<画像のpath>" />

!switchBtnの前に「!」がついていますが、「!」をつけると反対の値が設定されます。
この場合、初期値がfalseなので、クリックするとsetSwitchBtn()にはtrueが設定されます。

switchBtnの値を参照して、スイッチ画像のオン・オフを変更します。
三項演算子を使うとスッキリ記述できます。

switchBtn ? "on" : "off"

上記のように記述すると、switchBtnがtrueなら”on”、falseなら”off”が返却されます。この値を画像のURLに当てはめます。{}で囲むとjavascriptの記述ができます。
※onとoffそれぞれの画像を準備しています。

src={`https://m-kenomemo.com/sample/react-usestage/images/switch-${switchBtn ? "on" : "off"}.png`} 

スイッチ画像に関するコードを全て抜き出すと次のようになります。クリックしたらuseStateの値を更新して、画像を切り替えています。

<img onClick={() => setSwitchBtn(!switchBtn)} src={`https://m-kenomemo.com/sample/react-usestage/images/switch-${switchBtn ? "on" : "off"}.png`} />

useStateの値で電球の画像を変更する

先程のスイッチと同じように、電球の画像をオン・オフで変更します。

先程のスイッチと同じ仕組みです。switchBtnのfalse、trueの値を参照して画像を切り替えます。

<img src={`https://m-kenomemo.com/sample/react-usestage/images/light-${switchBtn ? "on" : "off"}.png`} />

電球の背景をCSSで変更する

最後に、電球のオン・オフに合わせて背景の色を変更します。スイッチがONなら電球が点いて背景が明るくなります。
こちらに関してもswitchBtnの値を参照して、背景変更用のclassを追加するだけです。

電球を表示している領域のclass名に、スイッチがON(switchBtnがtrue)なら「on」というclass名を追加します。スイッチがOFFなら空文字を返します。

<div className={`light_wrap ${switchBtn ? "on" : ""}`}>
 ----
</div>

これで背景のグラデーションを切り替えることができました。
通常のJavaScriptでも実装は可能ですが、コードが長く煩雑になると思います。
短いコードで記述でき、直感的にわかりやすいコードが記述できるのはすばらしいと思いました!

useStateでリアルタイムバリデーションを実装する

最後に、useStateを使って入力フォームのリアルタイムバリデーションを実装しました。
長いコードになったので、ハマったところだけメモしておきます。

実際の動きはこちらをご確認ください!

See the Pen inputTxtConfirm by donguri2020 (@m-ke) on CodePen.

うまく動かなくてハマったところ

入力した値を判定して、エラーが表示されるところまではうまくいきましたが、
一度表示されたエラーが消えませんでした、、。

エラーメッセージを表示している箇所を抜き出したのがこちらです。

const [errorMessageAccount, SetErrorMessageAccount] = useState([]);

const onChangAccount = (e)=> {
  const targetValue = e.target.value;
  let errorMessageAry = [];

  // 文字数のチェック
  if(targetValue.length < 8) {
    errorMessageAry = [...errorMessageAccount, "8文字以上で入力してください。"];
  } else if(targetValue.length > 12) {
    errorMessageAry = [...errorMessageAccount, "12文字以内で入力してください。"];
  }
  
  // 英数字のチェック
  if (!pattern.test(targetValue)) {
    errorMessageAry = [...errorMessageAccount, "英数字で入力してください"];
  }

  // エラーセット
  SetErrorMessageAccount(errorMessageAry);
}

return(
  <section>
    <p className="item_name">アカウント<span className="require">必須</span></p>
    <input type="text" onChange={onChangAccount} placeholder="8〜12文字の英数字で入力してください" />
    <!-- ▼▼エラーを表示する▼▼ -->
    {errorMessageAccount.map((error, index)=> <span key={index} className="error">{error}</span>)}
  </section>
)

useStateでエラーメッセージを管理します。
errorMessageAccount→エラーメッセージを配列で管理
SetErrorMessageAccount→エラーメッセージを更新

問題のあったところ

結論からいうと、原因はuseSetで更新する値にuseStateで管理している値を指定したことです

これだと良くわからないので、コードで確認します。
問題があった箇所に色付けしました。

 let errorMessageAry = [];
 
  // 文字数のチェック
  if(targetValue.length < 8) {
    errorMessageAry = [...errorMessageAccount, "8文字以上で入力してください。"];
  } else if(targetValue.length > 12) {
    errorMessageAry = [...errorMessageAccount, "12文字以内で入力してください。"];
  }
  
  // 英数字のチェック
  if (!pattern.test(targetValue)) {
    errorMessageAry = [...errorMessageAccount, "英数字で入力してください"];
  }

  // エラーセット
  SetErrorMessageAccount(errorMessageAry);
}

バリデーションチェックに該当したら、新しく作成した変数errorMessageAryにエラーメッセージを追加しています。最終的にはSetErrorMessageAccount()に渡すことでエラーメッセージを更新します。

エラーメッセージを追加する際、useStateで管理しているerrorMessageAccountを展開して追加していますが、

errorMessageAry = [...errorMessageAccount, "英数字で入力してください"];

これだと一度追加されたエラーメッセージが残ったままになります。
今回の場合、入力されるたびにエラーメッセージはクリアしたかったので、不要な処理でした、、。

改善したコード

最終的なコードは次の通りになりました!変更した箇所を色付けしました。

const [errorMessageAccount, SetErrorMessageAccount] = useState([]);

const onChangAccount = (e)=> {
  const targetValue = e.target.value;
  let errorMessageAry = [];

  // 文字数のチェック
  if(targetValue.length < 8) {
    errorMessageAry.push("8文字以上で入力してください。");
  } else if(targetValue.length > 12) {
    errorMessageAry.push("12文字以内で入力してください。");
  }
  
  // 英数字のチェック
  if (!pattern.test(targetValue)) {
    errorMessageAry.push("英数字で入力してください");
  }

  // エラーセット
  SetErrorMessageAccount(errorMessageAry);
}

return(
  <section>
    <p className="item_name">アカウント<span className="require">必須</span></p>
    <input type="text" onChange={onChangAccount} placeholder="8〜12文字の英数字で入力してください" />
    <!-- ▼▼エラーを表示する▼▼ -->
    {errorMessageAccount.map((error, index)=> <span key={index} className="error">{error}</span>)}
  </section>
)

errorMessageAryには、エラーメッセージだけを追加すれば良いので、メッセージはpush()で追加するようにしました。

errorMessageAry.push("英数字で入力してください");

これで問題なく動くようになりました。

まとめ

今回、useStateのサンプルを作ることで、なんとなく概念がわかってきました!
作成していて気になったのは、更新された値がどこからでも参照できる点です。

調べたところ、useStageの値を表示すると、コンポーネント内で再レンダリングされているようです。
この事を認識して使わないと思わぬバグを生みそうです、、。

コメント