🧑‍🦼

React Hook Form でshadcn/uiのSelectコンポーネントにフォーカスを合わせる

2025/02/27に公開

フロントエンド専門のWeb制作会社「株式会社トゥーアール」で代表をしている西畑です。

トゥーアールでは様々なフロントエンド開発をお手伝いしており最近ではshadcn/uiを利用したプロジェクトも増えてきております。

shadcn/uiは便利なのですが少しだけクセの強いところもあり今回はReact Hook FormSelectコンポーネントにフォーカスを指定するのに少しハマったので紹介します。

サンプル

まず、以下のような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要素のみが配置されたフォームが表示されます。

こちらのフォームの送信ボタンを押してみましょう。ZodSelectコンポーネントは必須項目にしているのでエラーが表示されます。

ここで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