Browse docs
Browse docs
A reactive form engine for building intelligent, dynamic forms with complex dependencies and conditional behavior.
Dashforge Form System eliminates the manual wiring required for dynamic forms. No scattered useEffect hooks. No prop drilling. No manual re-render coordination. Instead, you declare what fields depend on each other, and the system handles all orchestration automatically.
It's built on top of React Hook Form, adding a reactive engine, declarative reactions, and runtime state management. Together, they handle validation, submission, async coordination, and field dependencies without boilerplate.
Most form libraries handle static forms well: you define fields, add validation, submit the form. But real-world applications need dynamic behavior:
Without a system to orchestrate these behaviors, you end up with:
useEffect hooks throughout your form componentDashforge Form System provides three key primitives to solve this problem:
A state management layer built on Valtio that tracks field values, visibility, and dependencies. When a field changes, the engine automatically evaluates dependent rules and updates affected fields.
Define side effects as objects instead of imperative code. Specify what fields to watch, conditions to check, and effects to run. The system handles execution, async coordination, and stale response detection automatically.
Separate storage for async metadata like loading states, dynamic options, and fetch errors. This keeps your form values clean while providing rich UI feedback during async operations.
React Hook Form is excellent for form state and validation. But when fields need to interact—one field loading options based on another—you fall back to imperative code:
// Without Dashforge: Manual orchestration required
function AddressForm() {
const { watch, setValue } = useFormContext();
const [stateOptions, setStateOptions] = useState([]);
const [loading, setLoading] = useState(false);
const country = watch('country');
useEffect(() => {
if (!country) {
setStateOptions([]);
setValue('state', null);
return;
}
let cancelled = false;
setLoading(true);
fetchStates(country).then(states => {
if (!cancelled) {
setStateOptions(states);
setLoading(false);
}
});
return () => { cancelled = true; };
}, [country, setValue]);
return (
<>
<Select name="country" options={countries} />
<Select
name="state"
options={stateOptions}
loading={loading}
/>
</>
);
}With Dashforge, all orchestration is declarative:
// With Dashforge: Pure declaration
const reactions = [{
id: 'load-states',
watch: ['country'],
run: async (ctx) => {
const country = ctx.getValue('country');
if (!country) return;
const requestId = ctx.beginAsync('states');
ctx.setRuntime('state', { status: 'loading' });
const states = await fetchStates(country);
if (ctx.isLatest('states', requestId)) {
ctx.setRuntime('state', {
status: 'ready',
data: { options: states }
});
}
}
}];
<DashForm reactions={reactions}>
<Select name="country" options={countries} />
<Select name="state" optionsFromFieldData />
</DashForm>beginAsync/isLatest pattern built inHere's the orchestration flow when a user changes a field:
The Form System is separate from the UI components. Individual input components like TextField, Select, and Autocomplete are documented in the UI Components section.
This Form System section focuses on:
For documentation about specific input components (props, variants, examples), see the UI Components section.