はじめに
前回まではReact Hook FormとZodという入力フォームに必要な機能を揃えたライブラリを活用して「入力フォーム」を作り、「アカウント作成する」ボタンを押すと、入力内容がコンソールログで確認できるというところまで実装しました。
今回は入力内容をサーバーアクションに渡す処理を実装していきます。最終的にはデータベースに登録していくのですが、処理が複雑なため2回に記事を分けて解説していきます。
今回のゴール
この記事のゴールはアカウント登録画面で名前、メールアドレス、パスワードを入力。
その後、サーバーアクションで入力内容を確認し、エラーが発生した場合は、入力画面にエラー通知を返す処理を実装します。
サーバーアクションの作成
まずは新しくactions
フォルダを作成し、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%エラーが返るサーバーアクションとなります(後ほど修正します)
クライアントサイドでサーバーアクションを呼び出すよう設定
それでは作成したサーバーアクションを早速呼び出すようアカウント登録画面を修正していきます。
+ 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
をつけるのをお忘れなく。
サーバーアクションを呼び出した後にレスポンスをコンソールログで確認します。
アカウント登録画面で入力しボタンをクリックするとクライアントサイドではサーバーアクションのレスポンスが表示され、
サーバーサイドでは入力フォームの内容が表示されるかと思います。
このような表示となっていればOKです。
入力フォームの内容をサーバーサイドに渡すことができました。
サーバーアクションでの処理結果をユーザーに通知する
現状だとコンソールログにサーバーアクションの処理結果が表示されていますので、入力フォームに表示しアカウント作成が上手くいったか、失敗したかを通知させます。
実装としては大きく3段階に分けておこなっていきます。
- ①通知用のコンポーネント作成
- ②入力フォームにコンポーネントをセット
- ③サーバーアクションのレスポンスをもとに
onSubmit
を修正して通知用コンポーネントを表示
①通知用のコンポーネント作成
サーバーアクションが成功した場合、失敗した場合それぞれのコンポーネントを作成します。
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>
)
}
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を最初にインストールしたときに一緒に入ってくるアイコンセットとなります。
②入力フォームにコンポーネントをセット
次に作成したコンポーネントを入力フォームにセットします。
サーバーアクションのレスポンスに含まれるメッセージに関してはuseState
で状態管理します。
+ 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
を修正して、エラーが返る場合に入力フォームにエラー用のコンポーネントを表示できるよう設定します。
- 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}
を設定しておくのもいいかと思います。
動作確認
再び、アカウント登録画面で入力しボタンをクリックするとエラーメッセージが表示されるかと思います。
サーバーアクションを呼び出し結果をユーザーに通知することができました。
さいごに
今回はサーバーアクションと連携し入力フォームの入力内容を渡す処理を作成しました。
また処理が失敗したと仮定してその結果を入力フォームに戻しつつユーザーに通知する仕組みも実装しました。
次回はサーバーアクションが受け取った内容をもとにデータベースに登録していく機能を実装していきます。合わせて、現在は100%エラーが返るサーバーアクションとなっているところをデータベースの登録処理結果に基づいてメッセージ内容を切り替えられるよう実装していきます。
次の記事はこちら