0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React】Atmic designでFileアップロード機能を実装する

Posted at

今回は、Reactを使ってアトミックデザインを意識してFileアップロード機能を作ってみます。

完成イメージ

image.png

実装の方向性

実装の方向性は下記です。
File 専用のFieldを作る
value を持たせない
onChange だけを扱う

HTML仕様上、

Sample.html
<input value=?>

に渡せるのはstring / number / string[]だけで、
File オブジェクトは渡せません。

ディレクトリ構造

└── Frontend/
    ├── node_modules
    ├── public
    └── src/
        ├── assets
        └── components/
            ├── atoms/
            │   ├── Button.tsx
            │   ├── Checkbox.tsx
            │   ├── Input.tsx
            │   ├── InputFile.tsx
            │   └── Label.tsx
            ├── molecules/
            │   ├── FileField.tsx
            │   ├── FormField.tsx
            │   └── PasswordField.tsx
            ├── organisms/
            │   └── AdminUserForm.tsx
            ├── templates/
            │   └── AdminUserTemplate.tsx
            ├── pages/
            │   └── AdminUserRegisterPage.tsx
            ├── styles/
            │   └── global.css 👈自作
            ├── App.css
            ├── App.tsx
            ├── index.css
            ├── main.tsx
            ├── .env
            ├── eslint.config.js
            ├── index.html
            ├── package.json
            ├── package.lock.json
            ├── tsconfig.json
            ├── tsconfig.app.json
            ├── tsconfig.node.json
            └── vite.config.ts

実装

Atomレベル

src/components/atoms/InputFile.tsx
import React from "react";

type InputProps = Omit<
    React.ComponentPropsWithoutRef<"input">,"type"
>&{type :"file"};

export const InputFile = React.forwardRef<
    HTMLInputElement,
    InputProps
>(({ type="file", ...rest},ref)=>{
    return (
        <input
            ref={ref}
            type={type}
            {...rest}
            style={{
                padding: "8px", 
                width: "100%" ,
                border:"1px solid",
                borderRadius:"5px"
            }}
        />
    )
});


moleculesレベル

src/components/molecules/FileField.tsx
type Props = {
    label:string;
    onChange:(e:React.ChangeEvent<HTMLInputElement>)=>void;
}

export const FileFied:React.FC<Props> = ({ label,onChange })=>{
    return (
        <div style={{ marginBottom:"12px" }}>
            <label>{label}</label>
            <input
                type="file"
                accept="image/png, image/jpeg"
                onChange={onChange}
            />
        </div>
    )
}

Organismsレベル

src/components/organisms/AdminUserForm.tsx
import { useState } from "react";
import { PasswordField } from "../molecules/PasswordField";
import { FormField } from "../molecules/FormField";
import { Checkbox } from "../atoms/Checkbox";
import { Button } from "../atoms/Button";
import axios from "axios";
import { FileFied } from "../molecules/FileField";

export const AdminUserForm:React.FC = ()=>{
    const [id,setId] = useState("");
    const [name, setName] = useState("");
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [adminrole, setAdminrole] = useState(false);
    const [file, setFile] = useState<File | null>(null);
    // 管理者登録URL
    const create_admin_user_url = import.meta.env.VITE_NEW_ADMINUSER_CREATE;

    const handleSubmit = async(e:React.FormEvent)=>{
        e.preventDefault();
        const body = {
            id,
            name,
            email,
            password,
            adminrole
        };

        try{
            const response = await axios.post(create_admin_user_url,//"/api/new/adminusercreate"
                body,
                {
                    headers:{
                        "Content-Type": "application/json"
                    },
                    //withCredentials: true
                }
            )
            
            console.log(response);
            alert(response.data);
            
        }catch(error:any){
            if (error.response) {
            // サーバーが返したステータスコードがある場合
            alert(`エラー: ${error.response.status} ${error.response.data}`);
            } else if (error.request) {
                // リクエストは作られたがレスポンスなし
                alert("サーバーに到達できませんでした");
                return
            } else {
                // その他のエラー
                alert("予期せぬエラー");
                return
            }
            console.error(error);
            }

    };
    return (
        <form onSubmit={handleSubmit}>
            <FormField label="社員番号" value={id} onChange={(e)=>setId(e.target.value)}/>
            <FormField label="氏名" value={name} onChange={(e)=>setName(e.target.value)}/>
            <FormField type="email" label="メールアドレス" value={email} onChange={(e)=>setEmail(e.target.value)}/>
            <PasswordField value={password} onChange={(e) => setPassword(e.target.value)} />
            <FileFied label="画像ファイル" onChange={(e)=>{
                if(e.target.files && e.target.files.length > 0){
                    setFile(e.target.files[0])
                }
            }}/>
            <div style={{ marginBottom: "12px" }}>
                <label>
                    <Checkbox checked={adminrole} onChange={(e)=>setAdminrole(e.target.checked)}/>
                    管理者権限
                </label>
                <Button type="submit">登録</Button>
            </div>
        </form>
    )
}


サイト

【React】Atomic Designでのコンポーネントの汎用性を上げる方法(input要素編)

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?