Closed2

Reactで input[type="file"] を装飾するためのコンポーネントをつくる

catnosecatnose

アクセシビリティを考慮しつつ<input type="file" />を装飾する方法はこの記事にとても分かりやすくまとまっています。

https://zenn.dev/dqn/articles/7505cfa1bed278

個人的にもdisplay: noneを指定した<input type="file" /><button />から発火するやり方が装飾がしやすくていいなと思いました。

この記事で紹介されてる方法をReactで実装するにはざっくりと以下のようになると思います。

import { useRef } from 'react';

export const Example = () => {
  const inputFileRef = useRef<HTMLInputElement>(null);

  return (
    <>
      {/* クリック時に input[type="file"] を発火 */}
      <button onClick={() => inputFileRef.current?.click()} >
        ファイルを選択
      </button>
      <input ref={inputFileRef} style={{ display: 'none' }} />
    </>
  );
};

やることは単純なのですが、ファイル選択が必要になったコンポーネントごとにuseRefを使って、input[type="file"]display: noneにして…とゴニョゴニョやるのは面倒だなと思い、使い回しやすいコンポーネントを作ることにしました。

catnosecatnose

具体的には普通の<button>に近い形で使いつつ、合わせて<input type="file" />の属性も指定できるコンポーネントを作ることにしました。

👇 完成形

ButtonWithInputFile.tsx
import { useRef } from 'react';

/* inputやbuttonタグに渡せる属性の一覧 */
type NativeInputProps = React.InputHTMLAttributes<HTMLInputElement>;
type NativeButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;

/**
 * - input自体は常に非表示にするためstyleは指定不可とする
 * - refはボタンと紐付けるために使用するため指定不可する
 * - typeは`file`のみを受け付ける
 */
type InputProps = Omit<NativeInputProps, 'type' | 'style' | 'ref'> & { type: 'file' };

/**
 * - onClickはinputを発火するために使用するため指定不可とする
 */
type ButtonProps = Omit<NativeButtonProps, 'onClick'>;


type ButtonWithInputFileProps = ButtonProps & { inputProps: InputProps };

export const ButtonWithInputFile: React.FC<ButtonWithInputFileProps> = ({
  children,
  inputProps,
  ...buttonProps
}) => {
  const fileInputRef = useRef<HTMLInputElement>(null);

  return (
    <>
      <button
        {...buttonProps}
        onClick={() => fileInputRef.current?.click()}
      >
        {children}
      </button>
      <input {...inputProps} ref={fileInputRef} style={{ display: 'none' }} />
    </>
  );
};

使い方

ファイル選択ボタンを設置したい場所で以下のように呼び出します。

import { ButtonWithInputFile } from './ButtonWithInputFile';

export const Example = () => {
  function onSelectFile(e) {
      // ファイル選択時のイベントをハンドリング 
  }

  return (
    <>
      <h2>アイコンをアップロード</h2>

      <ButtonWithInputFile
        className={装飾用のクラス名を指定}
        // <input />の属性として渡す値を指定
        inputProps={{
          type: 'file',
          accept: 'image/png',
          onChange: onSelectFile
        }}
      >
         ファイルを選択
      </ButtonWithInputFile>
    </>
  );
};

こんな感じでファイル選択ボタンの装飾がやりやすくなりました。

このスクラップは2022/09/07にクローズされました