10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Reactで排他的なチェックボックスを実装する

Posted at

はじめに

訳あって、Reactで排他的なチェックボックスを実装する必要がありました。
ここでいう「排他的」は、グループにつき一個しかチェックできないこと(単一選択)を指しています。
別題(というかタイトル候補)は、「Reactでラジオボタンのようなチェックボックスを実装する」です。

コピペできる全コード付き + 解説ありなので、
初心者でも分かりやすい内容になっていると思います。

作成したチェックボックス

というわけで、2種類のチェックボックスを作成しました。
一つ目は必ず一つを選択しなければならない、チェックが外せないチェックボックスです。
二つ目はチェックが外せます。チェックの数は0か1ですね。
useStateでチェックの値を管理しています。

片方だけを読んでも分かるように、同じような構成で書いていまして、
重複もしくは全く一緒な内容は結構あります。
2種類とも読んでくださる方には申し訳ないですが、ご了承ください。

チェックを外せないパータン

完成イメージ

初期状態

image.png

選択後

image.png

全体コード

import React, { useState } from "react";

const MyForm = () => {
  const initializedData = {
    themeColor: "default"
  };

  const [data, setData] = useState(initializedData);

  const handleThemeColorChange = (e) => {
    const newData = { ...data, themeColor: e.target.value };
    setData(newData);
  };

  return (
    <form>
      テーマ:
      <input
        type="checkbox"
        id="themeDeafault"
        value="default"
        checked={data.themeColor === "default"}
        onChange={handleThemeColorChange}
      />
      <label htmlFor="themeDeafault">デフォルト </label>
      <input
        type="checkbox"
        id="themeRibbon"
        value="ribbon"
        checked={data.themeColor === "ribbon"}
        onChange={handleThemeColorChange}
      />
      <label htmlFor="themeRibbon">リボン </label>
      <input
        type="checkbox"
        id="themeDog"
        value="dog"
        checked={data.themeColor === "dog"}
        onChange={handleThemeColorChange}
      />
      <label htmlFor="themeDog">犬 </label>
      <input
        type="checkbox"
        id="themeNature"
        value="nature"
        checked={data.themeColor === "nature"}
        onChange={handleThemeColorChange}
      />
      <label htmlFor="themeNature">自然 </label>
    </form>
  );
};

解説

値の状態管理

  // データの初期値を定義
  const initializedData = {
    themeColor: "default"
  };
  
  // hooksで状態管理する
  const [data, setData] = useState(initializedData);

チェックボックスだけではなく、フォーム内に複数のデータがあるイメージだったので、
まずはフォーム全体をinitializedDataとします。
そして、今回のチェックボックスの値として、中にthemeColorを追加します。
必ずどれか選択しているとのことなので、
初期状態として、default(画面上の「デフォルト」)を選択させます。

準備が終わったら、hooksのuseStateを使って、
dataとsetDataを定義します。

HTML

  <input
    type="checkbox"
    id="themeRibbon"
    value="ribbon"
    checked={data.themeColor === "ribbon"}
    onChange={handleThemeColorChange}
  />
  <label htmlFor="themeRibbon">リボン </label>

4つのチェックボックスがありますが、
作りがまったく一緒なので、2番目の「リボン」で説明します。

上から順に説明しますので、まずは<input>からですね。
typeは入力欄の種別を定義できます。type="checkbox"でチェックボックスを作ります。
idはHTML部品の識別子になります。ページの中に、idを重複させない(一意的である)のが基本です。
valueにはチェックボックスが表している値を入れます。(詳細後述)
checkedがtrueの場合、チェックされる状態になります。falseの場合、チェックが外している状態になります。
今回は右側を{data.themeColor === "ribbon"}にすることによって、
動的にdata.themeColorの値によってチェックボックスの状態を変化させることができます。
"ribbon"だったら、チェックされる。"ribbon"以外の値だったら、チェックされない。)
onChangehandleThemeColorChangeという関数を入れました。(内容後述)
入力が変更される際に上記の関数を実行するようになります。
チェックボックスの場合、クリック = 値の変化(チェックする/チェックを外す)というところがあるため、
クリックされる度にonChangeは発火します。

次には<label>です。
htmlFor<input>idと同じ値を入れることによって、ラベルとチェックボックスを関連付けることができます。
この状態でラベルの「リボン」をクリックすると、チェックボックスがクリックされた時と同様の動きになります。

ちなみに、htmlForはReactの書き方になります。
通常のHTML(Reactビルド後)だと、以下のようになります。

<label for="themeRibbon">リボン </label>

こうなっている理由について、上記のリンクから引用します。

for は JavaScript での予約語であるため、React 要素では代わりに htmlFor を使用します。

onChangeイベント

先ほど、<input>onChangehandleThemeColorChangeという関数を入れました。
こちらの関数です。

  const handleThemeColorChange = (e) => {
    const newData = { ...data, themeColor: e.target.value };
    setData(newData);
  };

関数全体はアロー関数で書いており、functionを使った書き方はこうです。

  // 関数式の書き方
  function handleThemeColorChange(e) {
    const newData = { ...data, themeColor: e.target.value };
    setData(newData);
  };

また、TypeScriptの方のために、eの型も記載しておきます。

e: React.ChangeEvent<HTMLInputElement>

ここからは関数の中身を説明します。

一行目

    // ストアのデータを更新するために、新しい値を使って新しいデータを作成
    const newData = { ...data, themeColor: e.target.value };

...data:スプレッド構文で既存のデータを展開する。
, themeColor: :newData.themeColorの値を定義する際の左側。
e.target.value<input>valueにいれた値を取得。

 // リボンのチェックボックス(value="ribbon")がクリックされた際
 console.log(e.target.value === "ribbon");  // true

二行目

    // ストア更新
    setData(newData);

setDataにnewDataを渡すことによって、ストアのdataを書き換えることができます。

チェックを外せるパータン

完成イメージ

初期状態

image.png

選択後

image.png

全体コード

import React, { useState } from "react";

const MyForm = () => {
  const initializedData = {
    notifyFrequency: "",
  };

  const [data, setData] = useState(initializedData);

  const handleFrequencyChange = (e) => {
    const newValue = (e.target.value === data.notifyFrequency) ? "" : e.target.value;
    const newData = { ...data, notifyFrequency: newValue };
    setData(newData);
  };

  return (
    <form>
      通知頻度:
      <input
        type="checkbox"
        id="notifyAlways"
        value="always"
        checked={data.notifyFrequency === "always"}
        onChange={handleFrequencyChange}
      />
      <label htmlFor="notifyAlways">常時 </label>

      <input
        type="checkbox"
        id="notifyDaily"
        value="daily"
        checked={data.notifyFrequency === "daily"}
        onChange={handleFrequencyChange}
      />
      <label htmlFor="notifyDaily">毎日 </label>

      <input
        type="checkbox"
        id="notifyWeekly"
        value="weekly"
        checked={data.notifyFrequency === "weekly"}
        onChange={handleFrequencyChange}
      />
      <label htmlFor="notifyWeekly">毎週 </label>

      <input
        type="checkbox"
        id="notifyMonthly"
        value="monthly"
        checked={data.notifyFrequency === "monthly"}
        onChange={handleFrequencyChange}
      />
      <label htmlFor="notifyMonthly">毎月 </label>
    </form>
  );
};

解説

値の状態管理

  // データの初期値を定義
  const initializedData = {
    notifyFrequency: ""
  };
  
  // hooksで状態管理する
  const [data, setData] = useState(initializedData);

チェックボックスだけではなく、フォーム内に複数のデータがあるイメージだったので、
まずはフォーム全体をinitializedDataとします。
そして、今回のチェックボックスの値として、中にnotifyFrequencyを追加します。
初期状態として、何も選択されていないことを表す""(空文字列)にしました。

準備が終わったら、hooksのuseStateを使って、
dataとsetDataを定義します。

HTML

  <input
    type="checkbox"
    id="notifyDaily"
    value="daily"
    checked={data.notifyFrequency === "daily"}
    onChange={handleFrequencyChange}
  />
  <label htmlFor="notifyDaily">毎日 </label>

4つのチェックボックスがありますが、
作りがまったく一緒なので、2番目の「毎日」で説明します。

上から順に説明しますので、まずは<input>からですね。
typeは入力欄の種別を定義できます。type="checkbox"でチェックボックスを作ります。
idはHTML部品の識別子になります。ページの中に、idを重複させない(一意的である)のが基本です。
valueにはチェックボックスが表している値を入れます。(詳細後述)
checkedがtrueの場合、チェックされる状態になります。falseの場合、チェックが外している状態になります。
今回は右側を{data.notifyFrequency === "daily"}にすることによって、
動的にdata.notifyFrequencyの値によってチェックボックスの状態を変化させることができます。
"daily"だったら、チェックされる。"daily"以外の値だったら、チェックされない。)
onChangehandleFrequencyChangeという関数を入れました。(内容後述)
入力が変更される際に上記の関数を実行するようになります。
チェックボックスの場合、クリック = 値の変化(チェックする/チェックを外す)というところがあるため、
クリックされる度にonChangeは発火します。

次には<label>です。
htmlFor<input>idと同じ値を入れることによって、ラベルとチェックボックスを関連付けることができます。
この状態でラベルの「毎日」をクリックすると、チェックボックスがクリックされた時と同様の動きになります。

ちなみに、htmlForはReactの書き方になります。
通常のHTML(Reactビルド後)だと、以下のようになります。

<label for="notifyDaily">毎日 </label>

こうなっている理由について、上記のリンクから引用します。

for は JavaScript での予約語であるため、React 要素では代わりに htmlFor を使用します。

onChangeイベント

先ほど、<input>onChangehandleFrequencyChangeという関数を入れました。
こちらの関数です。

  const handleFrequencyChange = (e) => {
    const newValue = (e.target.value === data.notifyFrequency) ? "" : e.target.value;
    const newData = { ...data, notifyFrequency: newValue };
    setData(newData);
  };

関数全体はアロー関数で書いており、functionを使った書き方はこうです。

  // 関数式の書き方
  function handleFrequencyChange(e) {
    const newValue = (e.target.value === data.notifyFrequency) ? "" : e.target.value;
    const newData = { ...data, notifyFrequency: newValue };
    setData(newData);
  };

また、TypeScriptの方のために、eの型も記載しておきます。

e: React.ChangeEvent<HTMLInputElement>

ここからは関数の中身を説明します。

一行目

    // notifyFrequencyに入れるための新しい値を取得
    const newValue = (e.target.value === data.notifyFrequency) ? "" : e.target.value;

e.target.value<input>valueにいれた値を取得。
data.notifyFrequency:既存のnotifyFrequencyを取得。

 // 毎日のチェックボックス(value="daily")がクリックされた際
 console.log(e.target.value === "daily");  // true

外せるチェックボックスの場合、チェック済みだったら外す、そうでなければチェックするというロジックになります。
そのため、まずは「チェック済みか」を知る必要があります。

 (e.target.value === data.notifyFrequency)

この条件式はまさにそのためです。
両者を比較して、同じ場合(=チェック済み)はtrue、違う場合(=チェックされていない)はfalseになります。
後は三項演算子を使い、
newValue""もしくはe.target.valueを代入して宣言します。

二行目

    // ストアのデータを更新するために、新しい値を使って新しいデータを作成
    const newData = { ...data, notifyFrequency: newValue };

...data:スプレッド構文で既存のデータを展開する。
, notifyFrequency: :newData.notifyFrequencyの値を定義する際の左側。
newValue:新しい値。

三行目

    // ストア更新
    setData(newData);

setDataにnewDataを渡すことによって、ストアのdataを書き換えることができます。

さいごに

簡単な内容ではありますが、ついつい説明を足したくなります(汗)
長くなってしまいました。
ご質問やご指摘はいつでも受け付けますので、
コメントや編集リクエストをよろしくお願いします。

横並びしている4つのチェックボックスの作りがまったく一緒なので、
工夫すればとループで作成することもできます。
興味のある方はぜひ挑戦してみてください。

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?