Zustand・Zod・useRef を使ったステップ型フォームの実装

Zustand・Zod・useRef を使ったステップ型フォームの実装

Zustand で状態管理し、Zod でバリデーションを行いながら、React の useRef を活用してステップ型フォームを作成する方法を説明します。

## 1. 必要なライブラリのインストール
yarn add zustand zod @hookform/resolvers react-hook-form


## 2. Zustand を使ったフォームの状態管理

Zustand を使って、現在のステップとフォームデータを管理します。
```react
import { create } from "zustand";

interface FormState {
  step: number;
  data: {
    name: string;
    email: string;
    password: string;
  };
  setStep: (step: number) => void;
  updateData: (data: Partial<FormState["data"]>) => void;
}

export const useFormStore = create<FormState>((set) => ({
  step: 1,
  data: {
    name: "",
    email: "",
    password: "",
  },
  setStep: (step) => set({ step }),
  updateData: (data) => set((state) => ({ data: { ...state.data, ...data } })),
}));
```
## 3. Zod を使ったバリデーションスキーマ

各ステップごとにバリデーションスキーマを作成します。
```react
import { z } from "zod";

export const step1Schema = z.object({
  name: z.string().min(2, "名前は2文字以上で入力してください"),
});

export const step2Schema = z.object({
  email: z.string().email("正しいメールアドレスを入力してください"),
});

export const step3Schema = z.object({
  password: z.string().min(6, "パスワードは6文字以上必要です"),
});
```
## 4. 各ステップのフォームコンポーネント

React Hook Form を使い、Zod でバリデーションを適用します。

### Step1: 名前入力
```react
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { step1Schema } from "./schemas";
import { useFormStore } from "./store";

export default function Step1() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(step1Schema),
  });
  const { updateData, setStep } = useFormStore();

  const onSubmit = (values: { name: string }) => {
    updateData(values);
    setStep(2);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>名前:</label>
      <input {...register("name")} />
      {errors.name && <p>{errors.name.message}</p>}
      <button type="submit">次へ</button>
    </form>
  );
}
```
### Step2: メールアドレス入力
```react
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { step2Schema } from "./schemas";
import { useFormStore } from "./store";

export default function Step2() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(step2Schema),
  });
  const { updateData, setStep } = useFormStore();

  const onSubmit = (values: { email: string }) => {
    updateData(values);
    setStep(3);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>メールアドレス:</label>
      <input {...register("email")} />
      {errors.email && <p>{errors.email.message}</p>}
      <button type="button" onClick={() => setStep(1)}>戻る</button>
      <button type="submit">次へ</button>
    </form>
  );
}
```
### Step3: パスワード入力
```react
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { step3Schema } from "./schemas";
import { useFormStore } from "./store";

export default function Step3() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(step3Schema),
  });
  const { updateData, setStep } = useFormStore();

  const onSubmit = (values: { password: string }) => {
    updateData(values);
    alert("フォーム送信完了!");
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>パスワード:</label>
      <input type="password" {...register("password")} />
      {errors.password && <p>{errors.password.message}</p>}
      <button type="button" onClick={() => setStep(2)}>戻る</button>
      <button type="submit">送信</button>
    </form>
  );
}
```
## 5. ステップごとにフォームを切り替える

Zustand の step を監視しながら適切なコンポーネントを表示します。
```react
import { useFormStore } from "./store";
import Step1 from "./Step1";
import Step2 from "./Step2";
import Step3 from "./Step3";

export default function MultiStepForm() {
  const { step } = useFormStore();

  return (
    <div>
      <h1>ステップ型フォーム</h1>
      {step === 1 && <Step1 />}
      {step === 2 && <Step2 />}
      {step === 3 && <Step3 />}
    </div>
  );
}
```
## 6. useRef を活用してフォーカスを制御

フォーム送信後に次の入力フィールドへフォーカスを移動する場合、useRef を使って制御できます。

例えば Step1.tsx で useRef を使う場合:
```react
import { useRef, useEffect } from "react";

export default function Step1() {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []);

  return <input ref={inputRef} />;
}
```
この方法を各ステップに適用すると、ユーザーがスムーズに入力を進められます。

## 
まとめ
	•	Zustand: ステップと入力データの管理
	•	Zod: 各ステップのバリデーション
	•	React Hook Form: フォームのハンドリング
	•	useRef: フォーカス制御

この実装で、シンプルかつ拡張性の高いステップ型フォームが作成できます！