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.
- Schema — use qualquer string como
tipo:
{
"nome": "rut",
"tipo": "rut_chile",
"label": "RUT",
"ordem": 1,
"obrigatorio": true
}
- 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:
- O componente mapeado é usado — o renderer padrão é completamente ignorado
resolveProps/resolvePropsKeyainda executa antes de o componente receberfield- Condicionais,
validate[]ewarn[]ainda se aplicam normalmente