Desenvolvedores
src_
components_
ui_
Form.jsx

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:

  1. 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.
  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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,
};