Chakra UIでファイル選択画面をカスタマイズ!Reactで簡単実装ガイド

Programming React

はじめに

この記事では、ReactとChakra UIを使って、直感的で洗練されたファイルアップロードUIを作る方法を解説します。特に、ファイル選択を標準の<input>タグではなく、Chakra UIのButtonコンポーネントを利用してカスタマイズする方法を紹介します。

目次

  1. 背景と目的
  2. 使用する技術とライブラリ
  3. 実装手順
    1. ベースコードの確認
    2. <input>タグの非表示化
    3. useRefでファイル選択を制御
    4. UIの改良と動作確認
  4. 変更前後のコード比較
  5. おわりに

1. 背景と目的

デフォルトのファイル選択UIはシンプルですが、デザイン性に欠けることが多いです。Chakra UIのv2ではInputコンポーネントでファイル選択に関するUI設定が見当たらないため、オリジナルでChakra UIを利用しカスタマイズする方法を説明します。

2. 使用する技術とライブラリ

使用する技術はリンク先記事と同様です。

3. 実装手順

3.1 ベースコードの確認

ベースコードとしてリンク先記事のApp.tsxを利用します。

ベースコードApp.tsx

import React, { useState } from 'react';
import { Card, CardHeader, CardFooter, Heading, Flex, Button } from '@chakra-ui/react';
import axios from 'axios';

function App() {
  const [file, setFile] = useState<File | null>(null);
  const API_BASE_URL = 'http://localhost:8000';
  const selectFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
    let zipData = e.target.files
    if (zipData && zipData[0]) { 
      setFile(zipData[0])
    }
  }
  const fileUpload = () => {
    if(file) {
      const formData = new FormData();
      formData.append('file', file);
      axios({
        method: 'POST',
        headers: { 'Content-Type': 'multipart/form-data' },
        url: API_BASE_URL+'/upload',
        data: formData,
        withCredentials: true
      })
        .then(response => {
          console.log(response.data)
        })
        .catch(err => {
          console.log(err)
        })
    } 
  }

  return (
    <Flex justify='center' align='center' h='100vh'>
      <Card align='center'>
        <CardHeader>
          <Heading size='md'>ファイルアップロード</Heading>
        </CardHeader>
        <CardFooter>
          <input type='file' accept='application/zip' onChange={selectFile}/>
          <Button colorScheme='blue' onClick={fileUpload}>アップロード</Button>
        </CardFooter>
      </Card>
    </Flex>
  );
}

export default App;

3.2 <input>タグの非表示化

Chakra UIを利用するために標準の<input>タグは非表示にするために<input>タグにstyle属性を追加を行います。
ここで<input>タグにそのままstyle=”display:none”を記載すると以下のエラーメッセージでエラーとなります。

Type 'string' has no properties in common with type 'Properties<string | number, string & {}>'.ts(2559)
index.d.ts(1999, 9): The expected type comes from property 'style' which is declared here
 on type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'

これはCSSスタイルオブジェクトはReact.CSSPropertiesで定義する必要があるためです。
下記のように定義を行います。

  const inputStyle: CSSProperties = {
    display: 'none'
  }

定義したReact.CSSPropertiesを<input>タグに適用すると下記のように変更出来ます。

 <input type='file' style={{ ...inputStyle, display: 'none'}} accept='application/zip' onChange={selectFile}/>

3.3 useRefでファイル選択を制御

ここではファイル選択時のイベントを制御します。
そのために<input>タグにReactのuseRefを利用しref属性を追加します。

const inputRef = useRef<HTMLInputElement>(null);
<input type='file' style={{ ...inputStyle, display: 'none'}} accept='application/zip' onChange={selectFile} ref={inputRef}/>

非表示にしている<input>タグのクリックをButtonコンポーネントで制御できるように以下のようコードを追加していきます。

const select = () => {
  inputRef.current?.click();
  console.log(inputRef.current?.value);
}
------------ 
<Button colorScheme='blue' onClick={select}>ファイル選択</Button>

これにより非表示となっている<input>タグによるファイル選択をChakra UIのボタンコンポーネントのクリックによるトリガーできるようになりました。

4. 変更前後のコード比較

最終的な変更後のコードは以下となります。

import React, { useState, useRef, useEffect, CSSProperties } from 'react';
import { Card, CardHeader, CardFooter, Heading, Flex, Button } from '@chakra-ui/react';
import axios from 'axios';

function App() {
  const [file, setFile] = useState<File | null>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const API_BASE_URL = 'http://localhost:8000';
  const inputStyle: CSSProperties = {
    display: 'none'
  }

  const selectFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
    let zipData = e.target.files
    if (zipData && zipData[0]) { 
      setFile(zipData[0])
    }
  }
  const select = () => {
    inputRef.current?.click();
    console.log(inputRef.current?.value);
  }
  const fileUpload = () => {
    if(file) {
      const formData = new FormData();
      formData.append('file', file);
      axios({
        method: 'POST',
        headers: { 'Content-Type': 'multipart/form-data' },
        url: API_BASE_URL+'/upload',
        data: formData,
        withCredentials: true
      })
        .then(response => {
          console.log(response.data)
        })
        .catch(err => {
          console.log(err)
        })
    } 
  }

  return (
    <Flex justify='center' align='center' h='100vh'>
      <Card align='center'>
        <CardHeader>
          <Heading size='md'>ファイルアップロード</Heading>
        </CardHeader>
        <CardFooter>
          <input type='file' style={{ ...inputStyle, display: 'none'}} accept='application/zip' onChange={selectFile} ref={inputRef}/>
          <Button colorScheme='blue' onClick={select}>ファイル選択</Button>
          <Button colorScheme='blue' onClick={fileUpload}>アップロード</Button>
        </CardFooter>
      </Card>
    </Flex>
  );
}

export default App;

UIを確認してみるとファイル選択をChakra UIのボタンコンポーネントによって行えるようになっていることが確認できました。

5. おわりに

今回の記事では、ReactとChakra UIを使ったファイルアップロードUIのカスタマイズ方法を解説しました。これにより、より使いやすく、デザイン性の高いUIを実現できます。Chakra UIを活用して、さらに洗練されたアプリを作ってみましょう!