Skip to main content

Hooks & FormSpy

Access form state and the form API from any component inside <FormRenderer>.

Requirement

All hooks and <FormSpy> must be used inside the <FormRenderer> tree — for example, inside a custom component via componentMapper, or a child component passed via children to FormRenderer.

useFormApi

Returns the programmatic form API.

import { useFormApi } from "@schema-forms-data/renderer";

const api = useFormApi();

Return type

interface FormApi {
change: (name: string, value: unknown) => void;
submit: () => void;
reset: (values?: Record<string, unknown>) => void;
getState: () => FormStateSnapshot;
}

Example

import { useFormApi } from "@schema-forms-data/renderer";

function FillButton() {
const api = useFormApi();

return (
<button
type="button"
onClick={() => {
api.change("nome", "John Doe");
api.change("email", "john@example.com");
api.change("cidade", "New York");
}}
>
Fill with my data
</button>
);
}

useFormState

Returns the reactive form state — re-renders on every field change.

import { useFormState } from "@schema-forms-data/renderer";

const { values, errors, warnings, dirty, valid, submitting } = useFormState();

Return type

interface FormStateValue {
values: Record<string, unknown>;
errors: Record<string, string | undefined>;
warnings: Record<string, string>; // from warn[] validators
dirty: boolean;
valid: boolean;
submitting: boolean;
}

Example

import { useFormState } from "@schema-forms-data/renderer";

function StatusBar() {
const { dirty, valid, submitting, errors } = useFormState();

return (
<div className="status-bar">
{dirty && <span className="badge-warning">Unsaved changes</span>}
{!valid && (
<span className="badge-error">
{Object.keys(errors).length} field(s) with errors
</span>
)}
{submitting && <span>Saving…</span>}
</div>
);
}

useField

Returns the state of a single field — reactive.

import { useField } from "@schema-forms-data/renderer";

const { value, error, warning, dirty, touched, valid } = useField("email");

Return type

interface FieldState {
value: unknown;
error?: string;
warning?: string; // from warn[] validators
dirty: boolean;
touched: boolean;
valid: boolean;
}

Example

import { useField } from "@schema-forms-data/renderer";

function EmailHint() {
const email = useField("email");

if (!email.touched) return null;
if (email.error) return <p className="error">{email.error}</p>;
if (email.warning) return <p className="warning">{email.warning}</p>;
return <p className="success">✓ Valid email</p>;
}

useFieldApi

Returns input props and meta data for building fully custom field components.

import { useFieldApi } from "@schema-forms-data/renderer";

const { input, meta } = useFieldApi("email");

Return type

interface FieldApiReturn {
input: {
name: string;
value: unknown;
onChange: (...args: unknown[]) => void;
onBlur: () => void;
ref: React.Ref<unknown>;
};
meta: {
error?: string;
warning?: string;
touched: boolean;
dirty: boolean;
valid: boolean;
};
}

Example

import { useFieldApi } from "@schema-forms-data/renderer";

function MyEmailInput() {
const { input, meta } = useFieldApi("email");

return (
<div>
<input
type="email"
{...(input as React.InputHTMLAttributes<HTMLInputElement>)}
className={meta.error ? "input-error" : ""}
/>
{meta.error && <span className="error">{meta.error}</span>}
{meta.warning && <span className="warning">{meta.warning}</span>}
</div>
);
}

<FormSpy>

Observe form state as a React component. Useful when you can't use hooks (e.g. you need to render outside the form, or bridge to a non-hook context).

import { FormSpy } from "@schema-forms-data/renderer";

Three usage patterns

1. Function-child (render-prop)

<FormSpy>
{({ values, dirty, valid, errors, warnings, submitting }) => (
<pre className="debug">{JSON.stringify(values, null, 2)}</pre>
)}
</FormSpy>

2. render prop

<FormSpy render={({ dirty }) => (dirty ? <SaveIndicator /> : null)} />

3. onChange callback (no UI output)

<FormSpy
onChange={(state) => {
// autosave, analytics, cross-step state sync...
if (state.dirty) {
sessionStorage.setItem("draft", JSON.stringify(state.values));
}
}}
/>

Props

interface FormSpyProps {
children?: ((state: FormStateValue) => React.ReactNode) | React.ReactNode;
render?: (state: FormStateValue) => React.ReactNode;
onChange?: (state: FormStateValue) => void;
}

Complete example

import { FormRenderer, FormSpy } from "@schema-forms-data/renderer";

function MyForm() {
return (
<FormRenderer
schema={schema}
onComplete={async (values) => api.save(values)}
>
<FormSpy
onChange={(state) => {
analytics.track("form_change", {
dirty: state.dirty,
valid: state.valid,
});
}}
/>
</FormRenderer>
);
}

onValuesChange prop

The simplest way to observe field changes from outside the renderer tree — pass onValuesChange directly to <FormRenderer>:

<FormRenderer
schema={schema}
onValuesChange={(values) => {
// called on every field change in the current step
sessionStorage.setItem("form-draft", JSON.stringify(values));
}}
onComplete={handleComplete}
/>

getState (snapshot)

Use useFormApi().getState() to read the form state synchronously on demand (not reactive):

import { useFormApi } from "@schema-forms-data/renderer";

function SaveButton() {
const api = useFormApi();

const handleSave = () => {
const state = api.getState();
if (state.valid) {
api.submit();
} else {
console.log("Errors:", state.errors);
}
};

return <button onClick={handleSave}>Save</button>;
}
interface FormStateSnapshot {
values: Record<string, unknown>;
errors: Record<string, string | undefined>;
warnings: Record<string, string>;
dirty: boolean;
valid: boolean;
submitting: boolean;
}
Requisito

Todos os hooks e o <FormSpy> devem ser usados dentro da árvore do <FormRenderer> — por exemplo, dentro de um componente customizado via componentMapper, ou num componente filho passado via children do FormRenderer.

useFormApi

Retorna a API programática do formulário.

import { useFormApi } from "@schema-forms-data/renderer";

const api = useFormApi();

Tipo de retorno

interface FormApi {
change: (name: string, value: unknown) => void;
submit: () => void;
reset: (values?: Record<string, unknown>) => void;
getState: () => FormStateSnapshot;
}

Exemplo

import { useFormApi } from "@schema-forms-data/renderer";

function BotaoPreenchimento() {
const api = useFormApi();

return (
<button
type="button"
onClick={() => {
api.change("nome", "João Silva");
api.change("email", "joao@email.com");
api.change("cidade", "São Paulo");
}}
>
Preencher com meus dados
</button>
);
}

useFormState

Retorna o estado reativo do formulário — re-renderiza a cada mudança de campo.

import { useFormState } from "@schema-forms-data/renderer";

const { values, errors, warnings, dirty, valid, submitting } = useFormState();

Tipo de retorno

interface FormStateValue {
values: Record<string, unknown>;
errors: Record<string, string | undefined>;
warnings: Record<string, string>; // de validadores warn[]
dirty: boolean;
valid: boolean;
submitting: boolean;
}

Exemplo

import { useFormState } from "@schema-forms-data/renderer";

function BarraDeStatus() {
const { dirty, valid, submitting, errors } = useFormState();

return (
<div className="barra-status">
{dirty && <span className="badge-aviso">Alterações não salvas</span>}
{!valid && (
<span className="badge-erro">
{Object.keys(errors).length} campo(s) com erro
</span>
)}
{submitting && <span>Salvando…</span>}
</div>
);
}

useField

Retorna o estado de um único campo — reativo.

import { useField } from "@schema-forms-data/renderer";

const { value, error, warning, dirty, touched, valid } = useField("email");

Tipo de retorno

interface FieldState {
value: unknown;
error?: string;
warning?: string; // de validadores warn[]
dirty: boolean;
touched: boolean;
valid: boolean;
}

Exemplo

import { useField } from "@schema-forms-data/renderer";

function IndicadorCpf() {
const { value, error, touched } = useField("cpf");

if (!touched) return null;

return (
<div>
{error ? (
<span style={{ color: "red" }}>CPF inválido: {error}</span>
) : (
<span style={{ color: "green" }}>CPF válido: {String(value)}</span>
)}
</div>
);
}

useFieldApi

Retorna o input e o meta de um campo — útil para criar componentes de campo completamente customizados.

import { useFieldApi } from "@schema-forms-data/renderer";

const { input, meta } = useFieldApi("email");

Tipo de retorno

interface FieldApiReturn {
input: {
name: string;
value: unknown;
onChange: (value: unknown) => void;
onBlur: () => void;
ref: React.Ref<unknown>;
};
meta: {
error?: string;
warning?: string;
dirty: boolean;
touched: boolean;
valid: boolean;
};
}

FormSpy

Componente de render-prop para observar o estado do formulário.

import { FormSpy } from "@schema-forms-data/renderer";

// Autosave ao detectar mudanças
<FormSpy
onChange={(state) => {
sessionStorage.setItem("rascunho", JSON.stringify(state.values));
}}
>
{({ dirty, valid }) => (
<div className="rodape-formulario">
{dirty && <span>Você tem alterações não salvas</span>}
<button type="submit" disabled={!valid}>
Enviar
</button>
</div>
)}
</FormSpy>;

Props do FormSpy

PropTipoDescrição
onChange(state: FormStateValue) => voidCallback quando o estado muda
children(state: FormStateValue) => React.ReactNodeRender prop que recebe o estado
render(state: FormStateValue) => React.ReactNodeAlternativa ao children

### Example

```tsx
import { useField } from "@schema-forms-data/renderer";

function EmailHint() {
const email = useField("email");

if (!email.touched) return null;
if (email.error) return <p className="error">{email.error}</p>;
if (email.warning) return <p className="warning">⚠ {email.warning}</p>;
return <p className="success">✓ Valid email</p>;
}

useFieldApi

Returns input props and meta data for building custom field components.

import { useFieldApi } from "@schema-forms-data/renderer";

const { input, meta } = useFieldApi("email");

Return type

interface FieldApiReturn {
input: {
name: string;
value: unknown;
onChange: (...args: unknown[]) => void;
onBlur: () => void;
ref: React.Ref<unknown>;
};
meta: {
error?: string;
warning?: string;
touched: boolean;
dirty: boolean;
valid: boolean;
};
}

Example

import { useFieldApi } from "@schema-forms-data/renderer";

function MyEmailInput() {
const { input, meta } = useFieldApi("email");

return (
<div>
<input
type="email"
{...(input as React.InputHTMLAttributes<HTMLInputElement>)}
className={meta.error ? "input-error" : ""}
/>
{meta.error && <span className="error">{meta.error}</span>}
{meta.warning && <span className="warning">{meta.warning}</span>}
</div>
);
}

<FormSpy>

Observe form state as a React component. Useful when you can't use hooks (e.g. you need to render outside the form, or bridge to a non-hook context).

import { FormSpy } from "@schema-forms-data/renderer";

Three usage patterns

1. Function-child (render-prop)

<FormSpy>
{({ values, dirty, valid, errors, warnings, submitting }) => (
<pre className="debug">{JSON.stringify(values, null, 2)}</pre>
)}
</FormSpy>

2. render prop

<FormSpy render={({ dirty }) => (dirty ? <SaveIndicator /> : null)} />

3. onChange callback (no UI output)

<FormSpy
onChange={(state) => {
// autosave, analytics, cross-step state sync...
if (state.dirty) {
sessionStorage.setItem("draft", JSON.stringify(state.values));
}
}}
/>

Props

interface FormSpyProps {
children?: ((state: FormStateValue) => React.ReactNode) | React.ReactNode;
render?: (state: FormStateValue) => React.ReactNode;
onChange?: (state: FormStateValue) => void;
}

Complete example

import { FormRenderer, FormSpy } from "@schema-forms-data/renderer";

function MyForm() {
return (
<FormRenderer
schema={schema}
onComplete={async (values) => api.save(values)}
>
{/* FormSpy can be passed as children to FormRenderer in advanced setups,
or mounted inside a componentMapper component */}
<FormSpy
onChange={(state) => {
analytics.track("form_change", {
dirty: state.dirty,
valid: state.valid,
});
}}
/>
</FormRenderer>
);
}

onValuesChange prop

The simplest way to observe field changes from outside the renderer tree — pass onValuesChange directly to <FormRenderer>:

<FormRenderer
schema={schema}
onValuesChange={(values) => {
// called on every field change in the current step
sessionStorage.setItem("form-draft", JSON.stringify(values));
}}
onComplete={handleComplete}
/>

getState (snapshot)

Use useFormApi().getState() to read the form state synchronously on demand (not reactive):

import { useFormApi } from "@schema-forms-data/renderer";

function SaveButton() {
const api = useFormApi();

const handleSave = () => {
const state = api.getState();
if (state.valid) {
api.submit();
} else {
console.log("Errors:", state.errors);
}
};

return <button onClick={handleSave}>Save</button>;
}
interface FormStateSnapshot {
values: Record<string, unknown>;
errors: Record<string, string | undefined>;
warnings: Record<string, string>;
dirty: boolean;
valid: boolean;
submitting: boolean;
}