Модальное окно не закрывается при отправке

Я использую shadcn-ui для создания модального окна вместе с React-Hook-Form и Zod.

Моя главная цель — закрыть модальное окно при отправке формы.

Я визуализирую модальное окно внутри 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>
  )
}

Здесь находится вся логика моего модального окна:

import { zodResolver } from '@hookform/resolvers/zod'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { FormEvent, FormEventHandler, useCallback, 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.info({ isNewSupplierModalOpen })

  return {
    form,
    isPending,
    isNewSupplierModalOpen,
    onSubmit,
    openNewSupplierModal,
    closeNewSupplierModal,
    setIsNewSupplierModalOpen,
  }
}

А это его интерфейс:

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>
  )
}

Я не знаю, передал ли я в свои реквизиты open и onOpenChange какое-то состояние или функцию, но до сих пор мне это не удалось.

Заранее спасибо за вашу помощь.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
0
70
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Путаница возникает из-за того, что вы используете useNewSupplierModalController() дважды. Однажды в SupplierDataTable, который на самом деле обеспечивает видимость модального окна; и снова в NewSupplierModal.

Каждый раз, когда вы используете хук, он приобретает новое состояние. В каком-то смысле они являются «экземплярами». Если вы используете его более чем в одном месте, это означает, что вы полностью создали новое состояние. Пользовательские перехватчики — это способ абстрагировать общую логику в модуль многократного использования. Они не являются механизмом разделения состояния.

Поскольку обработчик отправки в NewSupplierModal взаимодействует с хуком, используемым внутри того же компонента, он не влияет на хук, используемый в SupplierDataTable (который управляет видимостью диалога). Вместо этого он переключит isNewSupplierModalOpen внутри экземпляра хука, где был захвачен обработчик отправки. И это даже не связано с реальным Dialog реквизитом, контролирующим его видимость.

Удалите вызов useNewSupplierModalController внутри NewSupplierModal и сохраните его SupplierDataTable. Затем вместо этого передайте необходимые вещи из SupplierDataTable в NewSupplierModal через реквизит.

Внутри data-table.tsx:

export function SupplierDataTable<TData, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) {
  const [sorting, setSorting] = useState<ColumnSort[]>([])
  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])

  const { isNewSupplierModalOpen, setIsNewSupplierModalOpen, form, onSubmit, isPending } =
    useNewSupplierModalController()

// ...

    <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 form = {form} onSubmit = {onSubmit} isPending = {isPending}/>
        </Dialog>

// ...

Внутри NewSupplierModal файла:

export function NewSupplierModal({form, onSubmit, isPending}) {
  return (
    <DialogContent className = "max-w-sm font-body">
      <DialogHeader>
        <DialogTitle>Novo Fornecedor</DialogTitle>

// ...

Как будет выглядеть мой интерфейс NewSupplierModal? Примерно так: интерфейс NewSupplierModalProps {onSubmit: (данные: Форма) => void; form: //... isPending: boolean; } Я не знаю, как напечатать форму.

mateusj 06.05.2024 15:13

Я выяснил, как правильно типизировать свойство form: это UseFormReturn, который является типом, возвращаемым функцией useForm библиотеки реакции-хука-формы, а затем Form, который является типом, выведенным из моей схемы проверки. Minhas типагенс ficaram assim: интерфейс NewSupplierModalProps { onSubmit: (data: Form) => void form: UseFormReturn<Form> isPending: boolean }

mateusj 06.05.2024 15:22

Поскольку я использую эту модальную структуру в других местах, могу ли я создать крючок, чтобы упростить ее? Например, мне пришлось бы создавать подобные интерфейсы в каждом модальном окне, которое я создаю, получая onSubmit, form и isPending. Стоит ли это создавать или не обязательно?

mateusj 06.05.2024 15:55

Вы можете просто создать для этого псевдоним типа и повторно использовать этот псевдоним типа в нескольких компонентах (используйте & для пересечения с дополнительными реквизитами). Или ts интерфейсы и расширяющий синтаксис

adsy 06.05.2024 17:37

Чувак, я пытался использовать ту же логику в другой части моего кода, где я визуализирую два модальных окна с разными триггерами. Возникла проблема, когда я передал свойство form в мой модальный компонент. Это ошибка ввода, поскольку изначально я получаю свойство типа Date, которое хочу отправить в API в виде строки. Знаешь, как я могу это решить?

mateusj 11.06.2024 23:22

Я использую этот метод zod, z.input и z.output, чтобы вставить схему ввода и вывода, но все равно получаю ту же ошибку.

mateusj 11.06.2024 23:23

Другие вопросы по теме

Ошибка: попытка Emit была предпринята до инициализации плагина Angular Webpack. при запуске npm
Определение типа
Попытка создать уникальную пару матчей для каждой команды, аналогичную формату швейцарской системы Лиги чемпионов УЕФА 2024/25
Угловой 17 | Angular Material 17.3.1: проблема с событиями поля формы Angular Material (фокус) и (размытие)
Реагировать на компонент машинописного текста с условными универсальными реквизитами, неправильно выводить тип
Ввод объекта с одинаковыми ключами, но разными типами их значений
Как динамически выбирать основной цвет в пользовательском интерфейсе Nuxt?
Angular 17 – невозможно привязать RouterLink, поскольку это неизвестное свойство a
Как использовать автономный компонент в неавтономном компоненте?
Ошибка компиляции TypScript. Выражения «await» верхнего уровня разрешены только в том случае, если для параметра «модуль» установлено значение