Pular para o conteúdo principal

Component Mapper

componentMapper permite substituir o renderizador built-in de qualquer FieldType com seu próprio componente React. Use para integrar design systems customizados, inputs de terceiros ou tipos de campo completamente novos.

Uso básico

import { FormRenderer } from "@schema-forms-data/renderer";
import { FieldType } from "@schema-forms-data/core";
import { MeuTextField, MeuDatePicker } from "./meus-componentes";

<FormRenderer
schema={schema}
componentMapper={{
[FieldType.TEXTO]: MeuTextField,
[FieldType.DATE]: MeuDatePicker,
}}
onComplete={handleComplete}
/>;

Qualquer tipo encontrado no componentMapper será renderizado com o componente mapeado em vez do widget padrão.

FieldComponentProps

Todo componente customizado recebe as seguintes props:

interface FieldComponentProps {
name: string; // identificador do campo (FormField.nome)
control: Control; // Control do react-hook-form do formulário pai
field: FormField; // o FormField completamente resolvido (após resolveProps + opcoesFromVar)
}

Criando um componente customizado

Use useController do react-hook-form para conectar o input:

import React from "react";
import { useController } from "react-hook-form";
import type { FieldComponentProps } from "@schema-forms-data/renderer";

export function MeuTextField({ name, control, field }: FieldComponentProps) {
const {
field: { value, onChange, onBlur, ref },
fieldState: { error },
} = useController({ name, control });

return (
<div className="meu-campo">
<label htmlFor={name}>{field.label}</label>
<input
id={name}
ref={ref}
value={(value as string) ?? ""}
onChange={onChange}
onBlur={onBlur}
disabled={field.isDisabled}
readOnly={field.isReadOnly}
aria-invalid={!!error}
/>
{error && <span className="erro">{error.message}</span>}
</div>
);
}

SELECT customizado com opções

import { useController } from "react-hook-form";
import type { FieldComponentProps } from "@schema-forms-data/renderer";

export function MeuSelect({ name, control, field }: FieldComponentProps) {
const {
field: rhf,
fieldState: { error },
} = useController({ name, control });

return (
<div>
<label>{field.label}</label>
<select {...rhf} value={(rhf.value as string) ?? ""}>
<option value="">Selecione…</option>
{field.opcoes?.map((opt) => (
<option key={opt.valor} value={opt.valor}>
{opt.label}
</option>
))}
</select>
{error && <p className="erro">{error.message}</p>}
</div>
);
}

Opções de opcoesFromVar já são resolvidas e mescladas em field.opcoes em tempo de render.

Tipos de campo customizados (fora do enum FieldType)

Você pode introduzir strings de tipo completamente novas além do enum FieldType built-in.

  1. Schema — use qualquer string como tipo:
{
"nome": "rut",
"tipo": "rut_chile",
"label": "RUT",
"ordem": 1,
"obrigatorio": true
}
  1. Mapper — registre a nova chave:
import { FormRenderer } from "@schema-forms-data/renderer";
import { RutInput } from "./RutInput";

<FormRenderer
schema={schema}
componentMapper={{
rut_chile: RutInput,
}}
onComplete={handleComplete}
/>;

Usando hooks dentro de componentes customizados

Como os componentes customizados são renderizados dentro da árvore do formulário, todos os hooks estão disponíveis:

import { useField, useFormApi } from "@schema-forms-data/renderer";
import type { FieldComponentProps } from "@schema-forms-data/renderer";

export function EmailInteligente({
name,
control,
field,
}: FieldComponentProps) {
const { value, error, warning } = useField(name);
const { change } = useFormApi();

return (
<div>
<input
value={(value as string) ?? ""}
onChange={(e) => change(name, e.target.value)}
/>
{warning && <p className="aviso">{warning}</p>}
{error && <p className="erro">{error}</p>}
</div>
);
}

Combinando componentMapper com validatorMapper

Renderização e validação customizadas funcionam independentemente — combine-as livremente:

<FormRenderer
schema={schema}
componentMapper={{
[FieldType.TEXTO]: MeuTextField,
[FieldType.SELECT]: MeuSelect,
}}
validatorMapper={{
emailUnico: async (value) => {
const existe = await api.emailExiste(String(value));
return existe ? "E-mail já cadastrado" : undefined;
},
}}
onComplete={handleComplete}
/>

Precedência

Quando componentMapper contém uma chave que corresponde ao tipo de um campo:

  1. O componente mapeado é usado — o renderer padrão é completamente ignorado
  2. resolveProps / resolvePropsKey ainda executa antes de o componente receber field
  3. Condicionais, validate[] e warn[] ainda se aplicam normalmente