Next.jsのサーバーアクションに登録フォームで入力したデータを渡す

2024/02/01に公開

はじめに

前回まではReact Hook FormとZodという入力フォームに必要な機能を揃えたライブラリを活用して「入力フォーム」を作り、「アカウント作成する」ボタンを押すと、入力内容がコンソールログで確認できるというところまで実装しました。
今回は入力内容をサーバーアクションに渡す処理を実装していきます。最終的にはデータベースに登録していくのですが、処理が複雑なため2回に記事を分けて解説していきます。

https://shinagawa-web.com/blogs/react-hook-form-validation-with-nextjs

今回のゴール

この記事のゴールはアカウント登録画面で名前、メールアドレス、パスワードを入力。
その後、サーバーアクションで入力内容を確認し、エラーが発生した場合は、入力画面にエラー通知を返す処理を実装します。

Image from Gyazo

サーバーアクションの作成

まずは新しくactionsフォルダを作成し、register.tsファイルを作成します。

register.ts
'use server'

import { z } from 'zod'
import { RegisterSchema } from '@/schema'

export const register = async (values: z.infer<typeof RegisterSchema>) => {
  const validatedFields = RegisterSchema.safeParse(values)
  console.log(validatedFields.data)

  if (!validatedFields.success) {
    return { error: '入力内容を修正してください' }
  }
  return { error: 'エラーが発生しました' }
}

'use server'と定義することでサーバー側で呼び出されるようになります。
前回の記事でご紹介したZodを使って取得した内容のバリデーションを行います。
Zodが便利なのはクライアントサイドだけでなくサーバーサイドでも同じように扱える点です。

受け取った値valuesに対してバリデーションをし、エラーが発生した場合は、
エラーメッセージを呼び出したクライアントに返すことができます。

今回は、クライアントサイドでも同じバリデーションを施しているため基本的には入力内容を修正してくださいのメッセージを返すことはありません。

なのでこの処理はスルーして最後のreturnでユーザーにメッセージを返します。
検証のため、今の設定では100%エラーが返るサーバーアクションとなります(後ほど修正します)

クライアントサイドでサーバーアクションを呼び出すよう設定

それでは作成したサーバーアクションを早速呼び出すようアカウント登録画面を修正していきます。

register-form.tsx
+ import { register } from '@/actions/register'

(※一部省略)

- const onSubmit = (values: z.infer<typeof RegisterSchema>) => {
+ const onSubmit = async (values: z.infer<typeof RegisterSchema>) => {
+   const response = await register(values)
-   console.log(values)
+   console.log(response)
}

サーバアクションは非同期処理に設定していますのでonSubmitでサーバアクションを呼び出す際にasyncをつけるのをお忘れなく。
サーバーアクションを呼び出した後にレスポンスをコンソールログで確認します。

アカウント登録画面で入力しボタンをクリックするとクライアントサイドではサーバーアクションのレスポンスが表示され、

Image from Gyazo

サーバーサイドでは入力フォームの内容が表示されるかと思います。

Image from Gyazo

このような表示となっていればOKです。
入力フォームの内容をサーバーサイドに渡すことができました。

サーバーアクションでの処理結果をユーザーに通知する

現状だとコンソールログにサーバーアクションの処理結果が表示されていますので、入力フォームに表示しアカウント作成が上手くいったか、失敗したかを通知させます。
実装としては大きく3段階に分けておこなっていきます。

  • ①通知用のコンポーネント作成
  • ②入力フォームにコンポーネントをセット
  • ③サーバーアクションのレスポンスをもとにonSubmitを修正して通知用コンポーネントを表示

①通知用のコンポーネント作成

サーバーアクションが成功した場合、失敗した場合それぞれのコンポーネントを作成します。

form-success.tsx
import { CheckCircledIcon } from '@radix-ui/react-icons'

interface FormSuccessProps {
  message?: string
}

export const FormSuccess = ({ message }: FormSuccessProps) => {
  if (!message) return null

  return (
    <div className="flex items-center gap-x-2 rounded-md bg-emerald-500/15 p-3 text-sm text-emerald-500">
      <CheckCircledIcon className="size-4" />
      <p>{message}</p>
    </div>
  )
}
form-error.tsx
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'

interface FormErrorProps {
  message?: string
}

export const FormError = ({ message }: FormErrorProps) => {
  if (!message) return null

  return (
    <div className="flex items-center gap-x-2 rounded-md bg-destructive/15 p-3 text-sm text-destructive">
      <ExclamationTriangleIcon className="size-4" />
      <p>{message}</p>
    </div>
  )
}

どちらのコンポーネントも基本的には同じ挙動でメッセージを受け取ったらコンポーネントを返す。
メッセージがなければ何も表示しないと言ったものとなります。
利用者に分かりやすくアイコンも使用していますが、@radix-ui/react-iconsは、shadcnを最初にインストールしたときに一緒に入ってくるアイコンセットとなります。

https://www.radix-ui.com/icons

②入力フォームにコンポーネントをセット

次に作成したコンポーネントを入力フォームにセットします。
サーバーアクションのレスポンスに含まれるメッセージに関してはuseStateで状態管理します。

register-form.tsx
+ import { useState } from 'react'
+ import { FormError } from '../form-error'
+ import { FormSuccess } from '../form-success'

(※一部省略)

export const RegisterForm = () => {
+   const [error, setError] = useState<string | undefined>("")
+   const [success, setSuccess] = useState<string | undefined>("")
  const form = useForm<z.infer<typeof RegisterSchema>>({
    resolver: zodResolver(RegisterSchema),
    defaultValues: {
      email: '',
      password: '',
      name: '',
    },
  })

(※一部省略)

            />
          </div>
+         <FormError message={error} />
+         <FormSuccess message={success} />
          <Button type="submit" className="w-full">
            アカウントを作成する
          </Button>
        </form>

③サーバーアクションのレスポンスをもとにonSubmitを修正して通知用コンポーネントを表示

最後にonSubmitを修正して、エラーが返る場合に入力フォームにエラー用のコンポーネントを表示できるよう設定します。

register-form.tsx
- import { useState } from 'react'
+ import { useState, useTransition } from 'react'

const onSubmit = async (values: z.infer<typeof RegisterSchema>) => {
-   const response = await register(values)
-   console.log(response)
+   setError('')
+   setSuccess('')
+   setTransition(async () => {
+     try {
+       const response = await register(values)
+       if (response.error) {
+         setError(response.error)
+       } else {
+         // setSuccess(response.success)
+       }
+     } catch (e) {
+       setError('エラーが発生しました')
+     }
+   })
}

(※一部省略)

-   <Button type="submit" className="w-full">
+   <Button type="submit" className="w-full" disabled={isPending}>
  アカウントを作成する
  </Button>

onSubmitの一番最初にメッセージの初期化を行います。前回のメッセージ内容が残っていますので消した上で、サーバーアクションの結果をもとに再びメッセージを格納します。

ReactのuseTransitionを使って、サーバーアクションの処理結果が返ってくるまでは、「アカウントを作成する」ボタンを非活性にしています。ユーザーが間違って2回ボタンを押すのを防止するためとなります。

また今回は実装しませんでしたが、Inputコンポーネントに対してもdisabled={isPending}を設定しておくのもいいかと思います。

動作確認

再び、アカウント登録画面で入力しボタンをクリックするとエラーメッセージが表示されるかと思います。

Image from Gyazo

サーバーアクションを呼び出し結果をユーザーに通知することができました。

さいごに

今回はサーバーアクションと連携し入力フォームの入力内容を渡す処理を作成しました。
また処理が失敗したと仮定してその結果を入力フォームに戻しつつユーザーに通知する仕組みも実装しました。
次回はサーバーアクションが受け取った内容をもとにデータベースに登録していく機能を実装していきます。合わせて、現在は100%エラーが返るサーバーアクションとなっているところをデータベースの登録処理結果に基づいてメッセージ内容を切り替えられるよう実装していきます。

次の記事はこちら

https://shinagawa-web.com/blogs/nextjs-server-actions-db-registration

記事に関するお問い合わせ📝

記事の内容に関するご質問、ご意見などは、下記よりお気軽にお問い合わせください。
ご質問フォームへ

技術支援などお仕事に関するお問い合わせ📄

技術支援やお仕事のご依頼に関するお問い合わせは、下記よりお気軽にお問い合わせください。
お問い合わせフォームへ