React Hook Form でshadcn/uiのSelectコンポーネントにフォーカスを合わせる
フロントエンド専門のWeb制作会社「株式会社トゥーアール」で代表をしている西畑です。
トゥーアールでは様々なフロントエンド開発をお手伝いしており最近ではshadcn/ui
を利用したプロジェクトも増えてきております。
shadcn/ui
は便利なのですが少しだけクセの強いところもあり今回はReact Hook Form
でSelectコンポーネント
にフォーカスを指定するのに少しハマったので紹介します。
サンプル
まず、以下のようなSelectコンポーネントとButtonコンポーネントのみがあるフォームを用意します。
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const formSchema = z.object({
userselect: z.string().min(1, {
message: "userselect required",
}),
});
export function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
userselect: "",
},
});
function onSubmit(values: z.infer<typeof formSchema>) {
form.setFocus("userselect");
console.log(values);
return false;
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="userselect"
render={({ field: { onChange, ...field } }) => (
<FormItem>
<FormLabel>Use Select</FormLabel>
<FormControl>
<Select {...field} onValueChange={onChange}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
);
}
これをブラウザで確認すると以下のようなselect要素とbutton要素のみが配置されたフォームが表示されます。
こちらのフォームの送信ボタンを押してみましょう。Zod
でSelectコンポーネント
は必須項目にしているのでエラーが表示されます。
ここでSelectコンポーネント
にフォーカスが当たっていないことが気になります。
React Hook Form
ではonError
時にエラーがあったUI要素にフォーカスを当ててくれるのですがSelectコンポーネント
の場合には当たりません。
解決方法
実はブラウザ上で見えているSelect部分はselect要素ではなくbutton要素になっておりselect要素にフォーカスを当ててもブラウザ上では視認できません。そのためbutton要素である<SelectTrigger>
にフォーカスを当てる必要があります。
対応方法はシンプルでrender時に取得できるfield
からref
を取り除き、代わりに<SelectTrigger>
にref
を当ててあげればフォーカスた当たるようになります。
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="userselect"
- render={({ field: { onChange, ...field } }) => (
+ render={({ field: { ref, onChange, ...field } }) => (
<FormItem>
<FormLabel>Use Select</FormLabel>
<FormControl>
<Select {...field} onValueChange={onChange}>
- <SelectTrigger className="w-[180px]">
+ <SelectTrigger className="w-[180px]" ref={ref}>
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
これでエラー時にセレクト部分にフォーカスがあたるようになります。
こういった見逃しがちなことを改善していくことでユーザーがより使いやすいUIになりますので参考にしてください。
Discussion