Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

ReactでUIコンポーネント作る粒度の温度差について

Checkboxのコンポーネントを作るになんでこれだけのコード量が必要になるのだろうか... Hooksもふんだんに使われている。

github.com

import React, { useCallback, useEffect, useRef, useState } from "react";
import styled from "styled-components";

import {
  CheckBoxIcon,
  CheckBoxOutlineBlankIcon,
} from "components/atoms/IconButton";

import Text from "components/atoms/Text";
import Flex from "components/layout/Flex";

export interface CheckBoxProps
  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "defaultValue"> {
  /**
   * 表示ラベル
   */
  label?: string;
}

const CheckBoxElement = styled.input`
  display: none;
`;

const Label = styled.label`
  cursor: pointer;
  margin-left: 6px;
  user-select: none;
`;

/**
 * チェックボックス
 */
const CheckBox = (props: CheckBoxProps) => {
  const { id, label, onChange, checked, ...rest } = props;
  const [isChecked, setIsChecked] = useState(checked);
  const ref = useRef<HTMLInputElement>(null);
  const onClick = useCallback(
    (e: React.MouseEvent) => {
      e.preventDefault();
      // チェックボックスを強制的にクリック
      ref.current?.click();
      setIsChecked((isChecked) => !isChecked);
    },
    [ref, setIsChecked]
  );

  useEffect(() => {
    // パラメータからの変更を受け付ける
    setIsChecked(checked ?? false);
  }, [checked]);

  return (
    <>
      <CheckBoxElement
        {...rest}
        ref={ref}
        type="checkbox"
        checked={isChecked}
        readOnly={!onChange}
        onChange={onChange}
      />
      <Flex alignItems="center">
        {/* チェックボックスのON/OFFの描画 */}
        {checked ?? isChecked ? (
          <CheckBoxIcon size={20} onClick={onClick} />
        ) : (
          <CheckBoxOutlineBlankIcon size={20} onClick={onClick} />
        )}
        {/* チェックボックスのラベル */}
        {label && label.length > 0 && (
          <Label htmlFor={id} onClick={onClick}>
            <Text>{label}</Text>
          </Label>
        )}
      </Flex>
    </>
  );
};

export default CheckBox;

個人的にはこれぐらいでよいのではと感じる。propでUIコンポーネントが受け取るパラメータをtypeで定義して代入する。場合によっては、useEffectやuseStateをonChangeに仕込むなりしたらいい。

weev.media

type Props = {
  //idを追加
  id: string;
  value: boolean;
  text: string;
  onChange: () => void;
};

export const Checkbox = ({ id, value, text, onChange }: Props) => {
  return (
    <div>
      <label htmlFor={id}>
        <div>
          <input
            type="checkbox"
            id={id}
            checked={value}
            onChange={() => {
              onChange();
            }}
          />
        </div>
        <div>
          {text}
        </div>
      </label>
    </div>
  );
};