logo

(続)ServerActions x conform でのトースト実装例

2025-03-13
22 days ago

開発環境

  • next 15.0.2
  • @conform-to/react 1.2.2
  • @conform-to/zod 1.2.2

前提

こちら↓の記事の続きの投稿になります。

また、kaitoさんがzennで投稿されていた記事をそのまま参考にさせていただきました。

詳しい解説はkaitoさんの下の記事を参考にしていただけますと幸いです。

本題

個人的にSeverActionsでのトースト実装例を模索していた中で、kaitoさんの記事を拝見し、「これにしよう!」となりました。

前提でも記載しましたが、詳しい解説はkaitoさんの記事を見ていただくとして、私が以前記事の中で実装したサンプルも書き換えてみます。

ServerActions x conform でのトースト実装例では、useCreateSkillというhooksを実装してformコンポーネントで利用する戦略を取っていましたが、withCallbacksを利用することでuseEffectを利用しなくてもコードの可読性が高い状態で成功時、エラー時の実装を組み込めるので、useCreateSkillは不要になりました。

withCallbacksの実装はkaitoさんの記事をご参照ください。

'use client';

import { useActionState } from 'react';

import { useRouter } from 'next/navigation';

import { useForm, getFormProps } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';

import { Field, FieldErrors } from '@/components/form/field';
import { FormInput } from '@/components/form/form-input';
import { FormMultiSelect } from '@/components/form/form-multi-select';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { toast } from '@/hooks/use-toast';
import { withCallbacks } from '@/lib/conform/with-callback';
import { SelectOptions } from '@/types';

import { createSkill } from '../../actions/create';
import { createSKillSchema } from '../../schemas/create';

type CreateSkillFormProps = {
  projectOptions?: SelectOptions;
};

export const CreateSkillForm = ({
  projectOptions = [],
}: CreateSkillFormProps) => {
  const router = useRouter();
  const [lastResult, action, isPending] = useActionState(
    withCallbacks(createSkill, {
      onError() {
        toast({
          variant: 'destructive',
          description: '気になるスキルを作成できませんでした',
        });
      },
      onSuccess() {
        toast({ description: '気になるスキルを作成しました' });
        router.back();
      },
    }),
    null,
  );
  const [form, fields] = useForm({
    id: 'create-skill',
    lastResult,
    onValidate({ formData }) {
      return parseWithZod(formData, { schema: createSKillSchema });
    },
    shouldValidate: 'onInput',
  });

  return (
    <form
      className="grid gap-4 px-1"
      {...getFormProps(form)}
      action={action}
      noValidate
    >
      <Field>
        <Label htmlFor={fields.name.id}>名称</Label>
        <FormInput meta={fields.name} type="text" />
        <FieldErrors errors={fields.name.errors} />
      </Field>
      <Field>
        <Label htmlFor={fields.url.id}>URL</Label>
        <FormInput meta={fields.url} type="text" />
        <FieldErrors errors={fields.url.errors} />
      </Field>
      <Field>
        <Label htmlFor={fields.projectIds.id}>プロジェクト</Label>
        <FormMultiSelect meta={fields.projectIds} options={projectOptions} />
        <FieldErrors errors={fields.projectIds.errors} />
      </Field>
      <Button
        type="submit"
        form="create-skill"
        disabled={isPending}
        className="w-full"
      >
        新規登録
      </Button>
    </form>
  );
};

useCreateSkillを利用していた時より、コード量自体は増えていますが、withCallbacksを意識せずにメンテナンスできるところがとても良いと感じています。

さいごに

色々と模索してましたが、現時点(2025/03/13)ではこれがベストプラクティスではないでしょうか・・

参照