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の値を表示すると、コンポーネント内で再レンダリングされているようです。
この事を認識して使わないと思わぬバグを生みそうです、、。
コメント