Explicação do código 🧑💻
Este código define uma série de componentes React personalizados para construir um formulário dinâmico e interativo, utilizando a biblioteca react-hook-form
para gerenciar o estado do formulário e validações. Vamos entender cada parte:
- Importações e Setup Inicial 📦
"use client";
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { Controller, FormProvider, useFormContext } from "react-hook-form";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
Aqui, importamos as dependências necessárias:
react
: Para manipulação de componentes React.@radix-ui/react-slot
: Para criar slots reutilizáveis em componentes.react-hook-form
: Para integrar controle de formulário no React.cn
: Função utilitária para adicionar classes CSS dinamicamente.Label
: Componente de rótulo para campos de formulário.
- Criação do Contexto do Formulário 🧑💼
const FormFieldContext = React.createContext({});
Criamos um contexto chamado FormFieldContext
para fornecer o nome do campo de formulário aos componentes que precisam dele.
- Componente FormField 📋
const FormField = ({ ...props }) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
O componente FormField
é usado para envolver cada campo de formulário e fornecer o nome do campo através do contexto FormFieldContext
. Ele utiliza o Controller
do react-hook-form
para gerenciar o estado e a validação do campo.
- Hook Customizado useFormField 🎣
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
O useFormField
é um hook customizado que fornece informações sobre o estado do campo no formulário, como erros e valores. Ele utiliza o contexto FormFieldContext
para acessar o nome do campo e FormItemContext
para acessar o id
do item. Ele também interage com o react-hook-form
para obter o estado atual do campo.
- Componente FormItem 📝
const FormItem = React.forwardRef(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
});
O componente FormItem
é um contêiner para o campo do formulário. Ele fornece um id
único para cada item através do FormItemContext
e aplica algumas classes de estilo, como space-y-2
, para adicionar espaçamento entre os componentes.
- Componente FormLabel 🏷
const FormLabel = React.forwardRef(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
);
});
O FormLabel
exibe o rótulo do campo. Se houver erro de validação, ele aplica a classe text-destructive
, alterando a cor do rótulo para indicar um erro.
- Componente FormControl 🛠
const FormControl = React.forwardRef(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
O FormControl
é responsável por exibir o controle do campo (como um input ou select). Ele também lida com a acessibilidade, utilizando aria-describedby
para fornecer descrições de erro ou mensagens adicionais.
- Componente FormDescription 📖
const FormDescription = React.forwardRef(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
);
});
O FormDescription
exibe uma descrição do campo de formulário. Ele é útil para fornecer informações adicionais sobre o que é esperado do usuário no campo.
- Componente FormMessage ⚠️
const FormMessage = React.forwardRef(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
);
});
O FormMessage
exibe uma mensagem de erro de validação se houver um erro no campo. Ele utiliza a propriedade error.message
do react-hook-form
para exibir a mensagem apropriada.
Código Completo 🧑💻
"use client";
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { Controller, FormProvider, useFormContext } from "react-hook-form";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
const Form = FormProvider;
const FormFieldContext = React.createContext({});
const FormField = ({ ...props }) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
const FormItemContext = React.createContext({});
const FormItem = React.forwardRef(({ className, ...props }, ref) => {
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
);
});
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField();
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
);
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
);
});
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
);
});
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
);
});
FormMessage.displayName = "FormMessage";
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};