useActionState
https://ja.react.dev/reference/react/useActionState
warning.icon React Canary 以前のバージョンでは、useFormState という名前だった
warning.icon RSC と CC の両方で利用できる
Server Actions の結果に基づいて State を更新するための Hook
code:ts
const state, formAction = useActionState(fn, initialState);
第 1 引数に Sever Action の関数、第 2 引数に初期状態を渡す
使用例
code:ts
import { useActionState } from 'react';
import { action } from './actions';
function MyComponent() {
const state, formAction = useActionState(action, null);
// ...
return (
<form action={formAction}>
{/* ... */}
</form>
);
}
第 1 引数の Server Action で返した値が次の状態値になる
code:ts
function action(currentState, formData) {
// ...
return 'next state';
}
より具体的な例
Server Actions のエラーハンドリングし、エラーがあった場合はメッセージを表示する
code:state.ts
export type FormState = {
message: string | null;
};
export const initialFormState: FormState = {
message: null,
};
code:action.ts
"use server";
function validateFormData(formData: FormData) {
const imageUrl = formData.get("imageUrl");
const name = formData.get("name");
const screenName = formData.get("screenName");
const bio = formData.get("bio");
if (
typeof imageUrl !== "string" ||
typeof name !== "string" ||
typeof screenName !== "string" ||
typeof bio !== "string"
) {
throw new Error("Validation error");
}
return { imageUrl, name, screenName, bio };
}
export async function updateUser(
_: FormState,
formData: FormData,
): Promise<FormState> {
const session = await getServerSession();
if (!session) {
return { message: "未認証です。再度ログインしてください" };
}
try {
const { imageUrl, name, screenName, bio } = validateFormData(formData);
const userId = session.user.id;
await prisma.$transaction([
prisma.user.update({
where: { id: userId },
data: { name, image: imageUrl },
}),
prisma.profile.update({
where: { userId },
data: { screenName, bio, userId },
}),
]);
revalidateTag(users/${userId});
} catch (err) {
if (err instanceof PrismaClientKnownRequestError) {
if (err.code === "P2002") {
return { message: "「表示名」がすでに使用されています" };
}
}
return { message: "エラーが発生しました" };
}
redirect("/profile");
}
code:index.tsx
export function MyProfileEditForm({ user, profile }: Props) {
const formState, formDispatch = useActionState(updateUser, initialFormState);
return (
<form action={formDispatch}>
{formState?.message && <AlertLabel>{formState.message}</AlertLabel>}
<div className={styles.module}>
<EditAvatar imageUrl={user.image} />
<EditMeta user={user} profile={profile} />
</div>
</form>
);
}
#React