Criei uma formulário para cadastro de produto, com muitos inputs. Decidi reutilizar esse formulário que criar o produto, para poder também atualizar o produto.
Zustand
Eu utilizo zodResolver
com useForm
no meu formulário:
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
Destaquei essa parte para mostrar que no handleProductForm
eu chamo o createOrUpdateProduct()
que é uma função de dentro do Zustand
que dei o nome de useManageProduct()
export const productFormSchema = z.object({
title: z.string().min(1, 'Este campo é obrigatório.'),
})
export type ProductFormData = z.infer<typeof productFormSchema>
export function ProductForm({ product, technicalProduct }: ProductFormProps) {
const { register, handleSubmit } = useForm<ProductFormData>({
resolver: zodResolver(productFormSchema),
})
const { createOrUpdateProduct, setProductActionType } = useManageProduct()
const { notifyError, notifySuccess } = useNotification()
const pathname = usePathname()
const isUpdateOrCreatePath = pathname === '/product-manage/register-product'
const handleProductForm = async (data: ProductFormData) => {
setProductActionType(isUpdateOrCreatePath)
const result = await createOrUpdateProduct(data)
if (result.success) {
return notifySuccess({ message: result.message, origin: 'server' })
} else {
return notifyError({ message: result.message, origin: 'server' })
}
}
return (
<form
id="product-form"
onSubmit={handleSubmit(handleProductForm)}
className="flex flex-col gap-4 space-y-10"
>
<label className="space-y-2">
<span>Nome</span>
<Input
type="text"
defaultValue={product?.title}
className="bg-transparent"
{...register('title')}
/>
</label>
...
Configuração no useManageProduct()
do Zustand
.
import { createProduct } from '@/actions/register/products'
import { updateProduct } from '@/actions/update/product'
import { ProductFormData } from '@/app/(admin)/product-manage/components/product-form/form'
import { create } from 'zustand'
export interface ManageProduct {
createOrUpdateProduct: (
data: ProductFormData,
) => Promise<{ success: boolean; message: string }>
setProductActionType: (isNewProduct: boolean) => void
isNewProduct: boolean
}
export const useManageProduct = create<ManageProduct>((set, get) => ({
isNewProduct: true,
setProductActionType(isNewProduct) {
set({ isNewProduct })
},
async createOrUpdateProduct(dataProduct) {
const { isNewProduct } = get()
if (isNewProduct) {
return await createProduct({ product: dataProduct })
} else {
return await updateProduct({ product: dataProduct })
}
},
}))
Com apenas essa configuração já é capaz de criar ou atualizar um produto utilizando o mesmo formulário.
No código podemos ver que a função createOrUpdateProduct
que tem a responsabilidade de passar os dados do produto para a função server side createProduct
ou updateProduct
.
E para saber para qual função passar esses dados é por meio da função setProductActionType
. Ela por sua vez, como vimos no arquivo de ProductForm
, recebe o isUpdateOrCreatePath
que é um booleano que irá definir se será true
ou false
, do resultado da comparação de pathname
com o trecho da rota '/product-manage/register-product'
que é a minha rota de criar produto.
...
const pathname = usePathname()
const isUpdateOrCreatePath = pathname === '/product-manage/register-product'
const handleProductForm = async (data: ProductFormData) => {
setProductActionType(isUpdateOrCreatePath)
...
E dessa forma a função createOrUpdateProduct
irá saber se passa os dados do produto para atualizar ou criar. Se isNewProduct
for true
é lógico que o administrador quer atualizar, se isNewProduct
for false
, ele pretende criar um novo produto.
async createOrUpdateProduct(dataProduct) {
const { isNewProduct } = get()
if (isNewProduct) {
return await createProduct({ product: dataProduct })
} else {
return await updateProduct({ product: dataProduct })
}
},
useContext
Uma alternativa ao Zustand
é utilizar o useContext
, que também funcionará, porém exigirá mais código. Escrever mais código não é algo negativo por si só, mas é importante avaliarmos cuidadosamente se, no nosso caso, essa abordagem trará benefícios suficientes.
'use client'
import { createProduct } from '@/actions/register/products'
import { updateProduct } from '@/actions/update/product'
import { ProductFormData } from '@/app/(admin)/product-manage/components/product-form/form'
import { ReactNode, createContext, useState } from 'react'
interface ManageProductFormContextType {
createOrUpdateProduct: (
data: ProductFormData,
) => Promise<{ success: boolean; message: string }>
setProductActionType: (isNewProduct: boolean) => void
}
interface ManageProductFormContextProps {
children: ReactNode
}
export const ManageProductFormContext = createContext(
{} as ManageProductFormContextType,
)
export function ManageProductFormContextProvider({
children,
}: ManageProductFormContextProps) {
const [isNewProduct, setIsNewProduct] = useState(true)
const setProductActionType = (isNewProduct: boolean) => {
setId(isNewProduct)
}
const createOrUpdateProduct = async (dataProduct: ProductFormData) => {
if (isNewProduct) {
return await createProduct({ product: dataProduct })
} else {
return await updateProduct({ product: dataProduct })
}
}
return (
<ManageProductFormContext.Provider
value={{ createOrUpdateProduct, captureProductId }}
>
{children}
</ManageProductFormContext.Provider>
)
}
É importante mencionar que, ao utilizar useContext
, é necessário envolver esse contexto que foi criado, por volta do {children}
no seu arquivo layout
se caso estiver usando Next.js.
<ManageProductFormContextProvider>
{children}
</ManageProductFormContextProvider>
Ou envolver seu contexto por volta de suas rotas se caso estiver usando React.js com Vite:
<ManageProductFormContextProvider>
<Routes />
</ManageProductFormContextProvider>
É importante saber que tem como chegar ao mesmo resultado sem utilizar nem o Zustand ou useContext, apenas fazendo uma lógica simples no próprio componente form para saber qual função enviar os dados do produto. O intuito aqui foi mostrar outras alternativas, e que em caso de adicionar mais complexidade é indicado fazer uso de umas dessas duas abordagem para que fique mais organizado.