Open3

MUIのCheckboxでCheckboxGroupを作成してみたメモ

koukou

CheckboxGroup本体

import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import MuiCheckbox from '@mui/material/Checkbox';
import { FormGroup } from '@mui/material';

type Props<T> = {
  checkboxesState: Record<string | number, boolean>;
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
  options: T[];
};

export const CheckboxGroup = <T extends { id: string | number; name: string }>({
  checkboxesState,
  onChange,
  options,
}: Props<T>) => {
  return (
    <FormControl component={'fieldset'}>
      <FormGroup>
        {options.map((option) => {
          return (
            <FormControlLabel
              key={option.id}
              label={option.name}
              control={
                <MuiCheckbox checked={checkboxesState[option.id]} onChange={onChange} name={String(option.id)} />
              }
            />
          );
        })}
      </FormGroup>
    </FormControl>
  );
};
koukou

使う側。改善の余地はまだありそう。

import type { NextPage } from 'next';
import type { AuthPageProps } from '@/components/page/auth/WithAuth';
import { Page } from '@/components/page/common/Page';
import { withAuth } from '@/components/page/auth/WithAuth';
import { useState } from 'react';
import { CheckboxGroup } from '@/components/ui/forms/Checkbox/CheckboxGroup';

const COURSES: { id: number; name: string }[] = [
  { id: 111, name: 'Aコース' },
  { id: 222, name: 'Bコース' },
  { id: 333, name: 'Cコース' },
];

const Index: NextPage<AuthPageProps> = ({ loginUser }) => {
  /**
   * initialState = {
    111: false,
    222: false,
    333: false,
  }
   */
  const initialState = COURSES.reduce((result, current) => {
    result[current.id] = false;
    return result;
  }, {} as Record<string, boolean>);

  const [checkboxes, setCheckboxes] = useState<Record<string, boolean>>(initialState);

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setCheckboxes({ ...checkboxes, [event.target.name]: event.target.checked });
  };

  return (
      <Page layout={'manager'} loginUser={loginUser}>
        <CheckboxGroup checkboxesState={checkboxes} onChange={handleChange} options={COURSES} />
      </Page>
  );
};

export default withAuth(Index);
koukou

TODO
複数チェックボックスの状態管理方法について考える。
配列が適しているケース、オブジェクトが適しているケース。

観点

  • 選択していない項目まで含めてチェック状況を扱いたい状況が存在するか、存在する場合はどういう状況か
// ① 選択している値を配列で持つ
const checkboxGroupState = ['banana', 'apple']

// ② 選択していない値まで含めた形でオブジェクトで持つ
const checkboxGroupState = {
  banana: true,
  grape: false,
  apple: true,
}

// ③ 選択している真偽値の状況を配列で持つ(第3の択が出現した)(参考:https://engineering.linecorp.com/ja/blog/line-securities-frontend-3)
const checkboxGroupState = [true, false, true]
// ①の更新(チェック状況配列に対象が含まれているかで条件分岐して更新)
const onChange = (event) => {
  const target = event.target.name
  setState(state.includes(target) ? state.filter((item) => item !== target) : [...state, target])
}

// ②の更新(チェック状態オブジェクトをスプレッド展開した上で、対象のチェック状況を更新)
const onChange = (event) => {
  setState({ ...state, [event.target.name]: event.target.checked })
}
// ①のチェック状態の判定
checked={state.includes(option.id)}

// ②のチェック状態の判定
checked={state[option.id]}

暫定結論

  • ほとんどのケースにおいては、選択している項目のみをリストに入れておく①の配列管理の方法で良さそう。
  • ②のオブジェクト管理の方法の利点は、選択していない項目まで含めた全情報を持てることにあるので、選択肢内のある要素の状態をきっかけに、別の要素の選択状態が変わりうるようなケースにおいては、オブジェクト管理の方が全体の状況を常に見ることができていいかもしれない。
  • ③の方法に関しては、選択している項目との対応関係が取れないので、ほとんど使うケースはなさそうに見える。