easy-forms
Core concepts

The store & rendering

The custom external store, topic-based per-field subscriptions, and why re-renders stay surgical.

Easy Forms does not use React Hook Form, Zustand, or React state for form data. It uses a custom external store consumed via React's useSyncExternalStore.

Topic-based subscriptions

The store maintains a per-field listener registry. When a field's value changes, the store replaces that field's state object with a new reference and notifies only:

  • the subscribers of that specific field, and
  • the form-state subscriber (for aggregate isDirty / isValid).

Other fields do not re-render. This is what keeps large forms fast without you sprinkling memo everywhere.

setValue('email', ...) ──▶ wake "email" subscribers + form-state subscriber
                           (the other 99 fields stay put)

The hooks

You rarely touch the store directly — the hooks subscribe to exactly the right topic:

HookSubscribes toReturns
useField(key)one fieldvalue, error, touched/dirty, setters
useFormState()form aggregateisDirty, isValid, isSubmitting, counts
useFormValues()all valuesthe current values object
useWatch(keys)specific fieldsjust those values
useGroup(id)one groupthe group's runtime props (e.g. hidden)

See the Hooks API for full signatures.

Field state

Each field tracks:

interface FieldState {
	value: unknown;
	initialValue: unknown;
	error: string | null;
	errors: Record<string, string>;   // named custom validators
	touched: boolean;
	dirty: boolean;
	validating: boolean;
	runtimeOverrides: Partial<RuntimeProps>; // written by propsDependsOn
	hiddenByAncestor: boolean;               // ancestor group hidden
}

Bring your own store

<EasyForm> creates a store for you. For advanced cases (driving values programmatically, sharing one store across components), create one yourself and pass it in:

import { createFormStore } from '@easy-forms/core';
import { EasyForm } from '@/components/easy-forms/easy-form';

const store = createFormStore({ initialValues: { qty: 1 } });
store.setValue('qty', 5);

<EasyForm store={store} schema={schema} onSubmit={...} />;

This is exactly how the homepage animation drives its "interaction beat".