Skip to main content

Reactive State

ReFormer uses Preact Signals for fine-grained reactivity.

How It Works​

Every node property is a Signal:

import { FieldNode } from '@reformer/core';
import { effect } from '@preact/signals-react';

const name = new FieldNode({ value: '' });

// Subscribe to changes
effect(() => {
console.log('Name changed:', name.value);
});

name.setValue('John'); // logs: "Name changed: John"
name.setValue('Jane'); // logs: "Name changed: Jane"

Reactive Properties​

All these properties are reactive:

const field = new FieldNode({ value: '' });

// Value
field.value; // reactive

// Validation state
field.valid; // reactive
field.invalid; // reactive
field.errors; // reactive

// Interaction state
field.touched; // reactive
field.dirty; // reactive

// UI state
field.disabled; // reactive
field.visible; // reactive

Computed Values​

GroupNode and ArrayNode compute their value from children:

const form = new GroupNode({
form: {
firstName: { value: '' },
lastName: { value: '' },
},
});

// form.value is computed from children
effect(() => {
console.log('Form value:', form.value);
});

form.controls.firstName.setValue('John');
// logs: { firstName: 'John', lastName: '' }

form.controls.lastName.setValue('Doe');
// logs: { firstName: 'John', lastName: 'Doe' }

React Integration​

The useFormControl hook subscribes to all field changes:

import { useFormControl } from '@reformer/core';

function Input({ field }: { field: FieldNode<string> }) {
const control = useFormControl(field);

// Component re-renders when any property changes
return (
<div>
<input
value={control.value}
onChange={(e) => control.setValue(e.target.value)}
disabled={control.disabled}
/>
{control.touched && control.errors?.required && <span>Required</span>}
</div>
);
}

Performance​

Signals provide fine-grained updates β€” only components using changed values re-render:

function Form() {
// This component doesn't re-render on field changes
return (
<form>
<NameField />
<EmailField />
<SubmitButton />
</form>
);
}

function NameField() {
const name = useFormControl(form.controls.name);
// Only re-renders when name changes
return <input value={name.value} />;
}

function EmailField() {
const email = useFormControl(form.controls.email);
// Only re-renders when email changes
return <input value={email.value} />;
}

Next Steps​

  • Nodes β€” Learn about node types
  • Validation β€” Add validation rules