🩻
shadcn/uiのInput type=fileでプレビューを表示する
はじめに
shadcn/ui の Form コンポーネントでファイルアップロード機能を作成する際に、プレビューを表示させる手順になります。
Form コンポーネントでは、簡単にフォームの UI を作成しながら React Hook Form と Zod を使用したバリデーションの実装が可能です。
完成物
コードを表示
"use client";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useState, ChangeEvent } from "react";
function getImageData(event: ChangeEvent<HTMLInputElement>) {
const dataTransfer = new DataTransfer();
Array.from(event.target.files!).forEach((image) =>
dataTransfer.items.add(image)
);
const files = dataTransfer.files;
const displayUrl = URL.createObjectURL(event.target.files![0]);
return { files, displayUrl };
}
export default function Page() {
const [preview, setPreview] = useState("");
return (
<>
<Form {...form}>
<form className="space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="picture"
render={({ field: { onChange, value, ...rest } }) => (
<>
<FormItem>
<FormLabel>ファイルアップロード</FormLabel>
<FormControl>
<Input
type="file"
{...rest}
onChange={(event) => {
const { files, displayUrl } = getImageData(event);
setPreview(displayUrl);
onChange(files);
}}
/>
</FormControl>
<FormDescription>ファイルを選択してください</FormDescription>
<FormMessage />
</FormItem>
</>
)}
/>
<Button type="submit">送信</Button>
</form>
</Form>
<div className="aspect-video max-w-[560px]">
{preview ? (
<img
src={preview}
alt=""
className="w-full h-full object-contain object-center"
/>
) : (
<div className="w-full h-full bg-background/70 rounded-lg border flex justify-center items-center">
<Image size={100} color="gray" />
</div>
)}
</div>
</>
);
}
※バリデーション設定等については省いています。
実装方法
shadcn/ui の Input コンポーネントでファイルアップロードのフィールドを作成し、Javascript のURL.createObjectURL
でオブジェクト URL を生成。
useState を用いてその URL を取得して表示させます。
ファイルアップロードのフィールドを作成する
全体像
<Form {...form}>
<form className="space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="picture"
render={({ field: { onChange, value, ...rest } }) => (
<>
<FormItem>
<FormLabel>ファイルアップロード</FormLabel>
<FormControl>
<Input
type="file"
{...rest}
onChange={(event) => {
const { files, displayUrl } = getImageData(event);
setPreview(displayUrl);
onChange(files);
}}
/>
</FormControl>
<FormDescription>ファイルを選択してください</FormDescription>
<FormMessage />
</FormItem>
</>
)}
/>
<Button type="submit">送信</Button>
</form>
</Form>
プレビュー機能に関係してくる箇所は主に以下になります。
<FormField
control={form.control}
name="picture"
render={({ field: { onChange, value, ...rest } }) => (
render
プロパティにonChange
とvalue
を指定することでフィールドの値が変更された時に特定の機能を与えることが可能になります。
...rest
にはその他のプロパティが保持されます。
<Input
type="file"
{...rest}
onChange={(event) => {
const { files, displayUrl } = getImageData(event);
setPreview(displayUrl);
onChange(files);
}}
/>
onChange
の中で後述のオブジェクト URL を生成し state を更新する処理を行います。
オブジェクト URL を生成する
function getImageData(event: ChangeEvent<HTMLInputElement>) {
const dataTransfer = new DataTransfer();
Array.from(event.target.files!).forEach((image) =>
dataTransfer.items.add(image)
);
const files = dataTransfer.files;
const displayUrl = URL.createObjectURL(event.target.files![0]);
return { files, displayUrl };
}
Input の onChange の中で引数を渡してこの関数が実行されています。
files
にアップロードされたローカルファイルを保持し、URL.createObjectURL
を使用してオブジェクト URL を生成してdisplayUrl
に代入しています。
プレビューを表示する
const [preview, setPreview] = useState("");
~~~
~~~
<Input
~~~
onChange={(event) => {
const { files, displayUrl} = getImageData(event)
setPreview(displayUrl);
}}
/>
プレビュー用の statepreview
を用意します。
ファイルがアップロードされgetImageData
が実行されるたびにdisplayUrl
が代入されます。
<div className="aspect-video max-w-[560px]">
{preview ? (
<img
src={preview}
alt=""
className="w-full h-full object-contain object-center"
/>
) : (
<div className="w-full h-full bg-background/70 rounded-lg border flex justify-center items-center">
<Image size={100} color="gray" />
</div>
)}
</div>
preview
にはオブジェクト URL が返ってくるので img タグの src にpreview
を表示すればプレビューが表示されます。
参考
Discussion