使用shadcn-ui+react-hook-form+zod时表单提交模态框不关闭
问题描述
使用shadcn-ui的模态框,搭配react-hook-form和zod处理表单,目标是表单提交后自动关闭模态框,但当前无法实现。
代码文件展示
1. data-table.tsx(渲染模态框的组件)
import { ... } from '@tanstack/react-table' import { Container } from 'lucide-react' import { useState } from 'react' import { DataTablePagination } from '@/components/data-table-pagination' import { Button } from '@/components/ui/button' import { Dialog, DialogTrigger } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Table, ... } from '@/components/ui/table' import { NewSupplierModal } from '../../modals/NewSupplierModal' import { useNewSupplierModalController } from '../../modals/NewSupplierModal/useNewSupplierModalController' interface DataTableProps<TData, TValue> { columns: ColumnDef<TData, TValue>[] data: TData[] } export function SupplierDataTable<TData, TValue>({ columns, data, }: DataTableProps<TData, TValue>) { const [sorting, setSorting] = useState<ColumnSort[]>([]) const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]) const { isNewSupplierModalOpen, setIsNewSupplierModalOpen } = useNewSupplierModalController() const table = useReactTable({ data, columns, ... }) return ( <div className="mx-auto container"> <div className="flex justify-between items-center mb-[10px]"> <Input placeholder="Buscar fornecedores" value={(table.getColumn('name')?.getFilterValue() as string) || ''} onChange={(e) => { table.getColumn('name')?.setFilterValue(e.target.value) }} className="max-w-sm" /> <Dialog open={isNewSupplierModalOpen} onOpenChange={setIsNewSupplierModalOpen} > <DialogTrigger asChild> <Button variant="outline" size="xs"> <Container className="mr-2 w-4 h-4" /> Adicionar novo fornecedor </Button> </DialogTrigger> <NewSupplierModal /> </Dialog> </div> <div className="mb-6 border rounded-md"> <Table> ... </Table> </div> {/* Pagination */} <DataTablePagination table={table} /> </div> ) }
2. useNewSupplierModalController.tsx(模态框逻辑控制)
import { zodResolver } from '@hookform/resolvers/zod' import { useMutation, useQueryClient } from '@tanstack/react-query' import { useState } from 'react' import { useForm } from 'react-hook-form' import toast from 'react-hot-toast' import * as z from 'zod' import supplierService from '@/services/supplierService' import { formSupplier } from '../../Table/SuppliersList/validators/form' type Form = z.infer<typeof formSupplier> export function useNewSupplierModalController() { const [isNewSupplierModalOpen, setIsNewSupplierModalOpen] = useState(false) const openNewSupplierModal = () => { setIsNewSupplierModalOpen(true) } const closeNewSupplierModal = () => { setIsNewSupplierModalOpen(false) } const queryClient = useQueryClient() const { mutateAsync, isPending } = useMutation({ mutationFn: supplierService.create, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['get-suppliers'], }) }, }) const form = useForm<Form>({ resolver: zodResolver(formSupplier), defaultValues: { supplierName: '', }, }) async function onSubmit(data: Form) { try { await mutateAsync({ name: data.supplierName, }) toast.success('Fornecedor cadastrado com sucesso!') form.reset({ supplierName: '', }) closeNewSupplierModal() } catch (error) { toast.error('Erro ao enviar a requisição') form.reset({ supplierName: '', }) } } console.log({ isNewSupplierModalOpen }) return { form, isPending, isNewSupplierModalOpen, onSubmit, openNewSupplierModal, closeNewSupplierModal, setIsNewSupplierModalOpen, } }
3. NewSupplierModal.tsx(模态框界面组件)
import { Spinner } from '@/components/spinner' import { Button } from '@/components/ui/button' import { DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { useNewSupplierModalController } from '../../modals/NewSupplierModal/useNewSupplierModalController' export function NewSupplierModal() { const { form, onSubmit, isPending } = useNewSupplierModalController() return ( <DialogContent className="max-w-sm font-body"> <DialogHeader> <DialogTitle>Novo Fornecedor</DialogTitle> </DialogHeader> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <FormField control={form.control} name="supplierName" render={({ field }) => ( <FormItem> <FormLabel>Nome do fornecedor</FormLabel> <FormControl> <Input placeholder="Informe o nome do fornecedor" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" disabled={isPending} className="p-5 w-full"> {isPending ? <Spinner /> : 'Cadastrar'} </Button> </form> </Form> </DialogContent> ) }
问题原因
你在两个组件中分别调用了useNewSupplierModalController:一次在SupplierDataTable,一次在NewSupplierModal。每个hook调用都会创建独立的状态实例,也就是说,SupplierDataTable中的isNewSupplierModalOpen和NewSupplierModal中的是完全不同的状态。当你在onSubmit里调用closeNewSupplierModal时,修改的是NewSupplierModal内的状态,而控制Dialog显示的是SupplierDataTable里的状态,所以模态框不会关闭。
解决方案
只在父组件SupplierDataTable中调用useNewSupplierModalController,然后将需要的属性传递给NewSupplierModal,避免重复调用hook导致状态不同步。
修改步骤:
更新SupplierDataTable组件
从hook中取出所有需要的属性,并传递给NewSupplierModal:export function SupplierDataTable<TData, TValue>({ columns, data, }: DataTableProps<TData, TValue>) { // ... 其他代码不变 const { isNewSupplierModalOpen, setIsNewSupplierModalOpen, form, onSubmit, isPending } = useNewSupplierModalController() // ... 其他代码不变 return ( // ... 其他代码不变 <Dialog open={isNewSupplierModalOpen} onOpenChange={setIsNewSupplierModalOpen} > <DialogTrigger asChild> {/* ... 按钮代码不变 */} </DialogTrigger> {/* 传递props给模态框 */} <NewSupplierModal form={form} onSubmit={onSubmit} isPending={isPending} /> </Dialog> // ... 其他代码不变 ) }更新NewSupplierModal组件
移除内部的hook调用,改为接收props:import { Spinner } from '@/components/spinner' import { Button } from '@/components/ui/button' import { DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { useForm } from 'react-hook-form' import { formSupplier } from '../../Table/SuppliersList/validators/form' import * as z from 'zod' type Form = z.infer<typeof formSupplier> // 定义props类型 interface NewSupplierModalProps { form: ReturnType<typeof useForm<Form>> onSubmit: (data: Form) => Promise<void> isPending: boolean } export function NewSupplierModal({ form, onSubmit, isPending }: NewSupplierModalProps) { return ( <DialogContent className="max-w-sm font-body"> <DialogHeader> <DialogTitle>Novo Fornecedor</DialogTitle> </DialogHeader> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <FormField control={form.control} name="supplierName" render={({ field }) => ( <FormItem> <FormLabel>Nome do fornecedor</FormLabel> <FormControl> <Input placeholder="Informe o nome do fornecedor" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" disabled={isPending} className="p-5 w-full"> {isPending ? <Spinner /> : 'Cadastrar'} </Button> </form> </Form> </DialogContent> ) }
这样修改后,closeNewSupplierModal修改的就是控制Dialog显示的那个状态,表单提交后模态框就会自动关闭了。
内容的提问来源于stack exchange,提问作者mateusj
相关产品推荐
相关产品推荐

