📑

Server Actionsを使ったフォームで通知を出す方法を考える

2024/06/16に公開

方法

  1. onSubmitを使う
    onSubmitの中で関数を呼び出し、成否に応じてtoastを実行する。
    フォームの処理中を判断するために、useTransitionを使う。
onSubmitを使ったフォームの実装
form.tsx
"use client";

import { Button, Loader, TextInput } from "@mantine/core";
import { useForm, zodResolver } from "@mantine/form";
import { useTransition } from "react";
import { toast } from "sonner";
import type { z } from "zod";
import { action } from "~/app/actions";
import { formSchema } from "~/app/schema";

export function Form() {
  const [isPending, startTransition] = useTransition();
  const form = useForm({
    mode: "uncontrolled",
    initialValues: {
      email: "",
      message: "",
    },

    validate: zodResolver(formSchema),
    validateInputOnBlur: true,
  });

  const handleSubmit = (values: z.infer<typeof formSchema>) => {
    startTransition(async () => {
      try {
        await action(values);
        toast.success("success!");
      } catch {
        toast.error("error!");
      }
    });
  };

  return (
    <form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
      <TextInput
        required
        withAsterisk
        label="Email"
        placeholder="your@email.com"
        name="email"
        type="email"
        key={form.key("email")}
        {...form.getInputProps("email")}
      />
      <TextInput
        required
        withAsterisk
        label="Message"
        placeholder="message"
        name="message"
        type="text"
        key={form.key("message")}
        {...form.getInputProps("message")}
      />
      <Button disabled={isPending} type="submit">
        {isPending && <Loader size="sm" mr="md" />}
        送信
      </Button>
    </form>
  );
}

https://github.com/hiropi1224/form-demo/blob/main/app/mantine

  1. useEffectを使う
    useEffectを使ってuseActionStateのstateから判定してtoastを実行する。
useEffectを使ったフォームの実装
form.tsx
"use client";

import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { getZodConstraint, parseWithZod } from "@conform-to/zod";
import { Button, Loader, TextInput } from "@mantine/core";
import { useActionState, useEffect } from "react";
import { toast } from "sonner";
import { formAction } from "~/app/actions";
import { formSchema } from "~/app/schema";

export function Form() {
  const [lastResult, action, isPending] = useActionState(formAction, undefined);

  const [form, fields] = useForm({
    constraint: getZodConstraint(formSchema),
    lastResult,
    onValidate({ formData }) {
      return parseWithZod(formData, { schema: formSchema });
    },
    shouldValidate: "onBlur",
  });

  useEffect(() => {
    if (lastResult && lastResult.status === "error") {
      toast("error");
    } else if (lastResult && lastResult.status === "success") {
      toast("success");
    }
  }, [lastResult]);

  return (
    <form {...getFormProps(form)} action={action}>
      <TextInput
        required
        withAsterisk
        label="Email"
        placeholder="your@email.com"
        {...getInputProps(fields.email, { type: "email" })}
        error={fields.email.errors}
      />
      <TextInput
        required
        withAsterisk
        label="Message"
        placeholder="message"
        {...getInputProps(fields.message, { type: "text" })}
        error={fields.message.errors}
      />
      <Button disabled={isPending} type="submit">
        {isPending && <Loader size="sm" mr={4} />}
        送信
      </Button>
    </form>
  );
}

https://github.com/hiropi1224/form-demo/blob/main/app/use-effect

Discussion