Conectando ao seu Backend
O FormRenderer é completamente headless em relação a dados — você injeta suas próprias funções para upload de arquivos, busca de CEP, recursos de termos e props dinâmicas de campos.
uploadFile
Chamado quando o usuário seleciona um arquivo em um campo FILE.
<FormRenderer
schema={schema}
uploadFile={async (file, fieldName) => {
const form = new FormData();
form.append('file', file);
form.append('campo', fieldName);
const res = await fetch('/api/upload', {
method: 'POST',
body: form,
headers: { Authorization: `Bearer ${token}` },
});
const { id } = await res.json();
return id; // deve retornar uma string (URL ou ID do upload)
}}
onComplete={...}
/>
O valor do campo armazenado será a string retornada pela função.
cepLookup
Chamado quando o usuário termina de digitar um CEP em um campo CEP. O renderer preenche automaticamente os campos irmãos logradouro, bairro, cidade e estado com o resultado.
<FormRenderer
schema={schema}
cepLookup={async (cep) => {
const res = await fetch(`https://viacep.com.br/ws/${cep}/json/`);
const data = await res.json();
return {
logradouro: data.logradouro,
bairro: data.bairro,
cidade: data.localidade,
estado: data.uf,
erro: !!data.erro,
};
}}
onComplete={...}
/>
Se cepLookup não for fornecido, o campo CEP usa a API pública ViaCEP como fallback.
resolveTermsUploadUrl
Chamado quando um campo TERMS tem um termoPdfUploadId — resolve o uploadId para uma URL pública de PDF.
<FormRenderer
schema={schema}
resolveTermsUploadUrl={async (uploadId) => {
const res = await fetch(`/api/uploads/${uploadId}/url`);
const { url } = await res.json();
return url;
}}
onComplete={...}
/>
fieldResolvers — Props dinâmicas de campos
Use fieldResolvers para carregar props de campo em tempo de render com base no estado do formulário ou em dados externos.
1. Defina a chave no schema
{
"nome": "tamanho_camiseta",
"tipo": "select",
"label": "Tamanho da camiseta",
"resolvePropsKey": "opcoes_camiseta"
}
2. Registre o resolver no renderer
<FormRenderer
schema={schema}
externalData={{ "evento.tamanhosCamiseta": ["P", "M", "G", "GG"] }}
fieldResolvers={{
opcoes_camiseta: (field, _values, external) => ({
opcoes: (external['evento.tamanhosCamiseta'] as string[])
?.map(t => ({ valor: t, label: t })) ?? field.opcoes,
}),
// Desabilitar campo com base em outro valor
forma_pagamento: (_field, values) => ({
isDisabled: values['pagamento_confirmado'] === true,
}),
}}
onComplete={...}
/>
O resolver recebe (field, formValues, externalData) e retorna um Partial<FormField> que é mesclado nas props do campo.
externalData — Dados de contexto externo
Use externalData para disponibilizar dados externos ao formulário — para interpolação de template vars, condicionais source: 'evento' e resolvers.
<FormRenderer
schema={schema}
externalData={{
"evento.nome": "Acampamento Verão 2026",
"evento.dataInicioEvento": "2026-01-15",
"evento.valor": 15000,
"evento.vagasTotal": 100,
"evento.permiteMenores": true,
}}
onComplete={...}
/>
No schema, referencie com {{evento.nome}} em labels/hints/títulos ou com source: "evento" em condicionais.
fieldErrors — Erros do servidor
Após um submit com erros 422 do servidor, passe os erros de volta para os campos:
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
async function handleComplete(values) {
const res = await fetch("/api/inscricao", {
method: "POST",
body: JSON.stringify(values),
});
if (res.status === 422) {
const { errors } = await res.json();
// { email: "E-mail já cadastrado", cpf: "CPF inválido" }
setFieldErrors(errors);
return;
}
}
<FormRenderer
schema={schema}
fieldErrors={fieldErrors}
onComplete={handleComplete}
/>;