文章
nextjs 模态框表单 demo
1、 _types
import z from "zod";
// 1. 定义表单字段的验证 schema
const userSchema = z.object({
fullName: z.string().min(1, "姓名不能为空!").max(255),
email: z.string().email("输入邮箱格式错误!"),
});
// 2. 推导 TypeScript 类型
type UserSchema = z.infer<typeof userSchema>;
// 3. 默认值(从当前用户状态来)
const userDefaultValues: UserSchema = {
fullName: "", // 实际使用时会被当前用户数据覆盖
email: "",
};
export {userDefaultValues, userSchema, type UserSchema};2、_services
import { requestAction } from "@/lib/request-action";
import { useQuery } from "@tanstack/react-query";
export const useCurrentUser = () =>
useQuery({
queryKey: ["currentUser"],
queryFn: async () => {
const result = await requestAction("client", "/api/user/profile", {
method: "GET",
});
return result.data;
},
staleTime: 1000 * 60, // 缓存 1 分钟
});3、_components
"use client";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import { ControlledInput } from "@/components/controlled/controlled-input";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import { userSchema, UserSchema } from "@/app/(protected)/dashboard/settings/user/_types/user-schema";
import { zodResolver } from "@hookform/resolvers/zod";
import { requestAction } from "@/lib/request-action";
import { ApiResponseType } from "@/types/req-types";
interface UserFormDialogProps {
currentUser: {
id: number;
fullName: string;
email: string;
};
}
export function UserFormDialog({ currentUser }: UserFormDialogProps) {
const [open, setOpen] = useState(false);
const queryClient = useQueryClient();
const [cooldown, setCooldown] = useState(false);
const form = useForm<UserSchema>({
defaultValues: {
fullName: currentUser.fullName,
email: currentUser.email,
},
resolver: zodResolver(userSchema),
});
useEffect(() => {
if (open) {
form.reset({
fullName: currentUser.fullName,
email: currentUser.email,
});
}
}, [open, currentUser, form]);
const mutation = useMutation({
mutationFn: (data: UserSchema) =>
requestAction("client", "/api/user/profile/update", {
method: "PUT",
body: JSON.stringify({ id: currentUser.id, ...data })
}),
onSuccess: (result: ApiResponseType) => {
if (result.success) {
// 同时刷新当前用户 query,让其他组件也更新
queryClient.invalidateQueries({ queryKey: ["currentUser"] });
toast.success("修改成功");
} else {
toast.error(result.message);
}
}
});
const onSubmit: SubmitHandler<UserSchema> = (data) => {
// 防止重复提交(逻辑层防护)
if (mutation.isPending || cooldown) {
return;
}
setCooldown(true);
mutation.mutate(data, {
onSuccess: () => setOpen(false),
onSettled: () => setTimeout(() => setCooldown(false), 500)
});
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>编辑信息</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>编辑用户信息</DialogTitle>
</DialogHeader>
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<ControlledInput name="fullName" label="姓名" placeholder="请输入姓名" />
<ControlledInput name="email" label="邮箱" placeholder="请输入邮箱" type="email" />
<Button type="submit" disabled={mutation.isPending || cooldown}>确认修改</Button>
</form>
</FormProvider>
</DialogContent>
</Dialog>
);
}