Form Adapters
A FormAdapter handles all state management concerns — registration, validation, dirty tracking, submission — while staying completely decoupled from the UI.
Built-in Form Adapters
| Export | Library | Description |
|---|---|---|
RavenRHFAdapter | React Hook Form | Production-ready. Supports async validation, DevTools integration, per-form state isolation |
RavenAntDAdapter | None (built-in) | Lightweight in-memory adapter — useful for demos and simple forms. Zero extra dependencies |
FormAdapter Interface
interface FormAdapter {
/** Wraps the form tree; must call onSubmit when submitted */
Provider: ComponentType<FormAdapterProviderProps>;
/** Returns binding for a single field by name */
useField: (name: string) => FieldBinding;
/** Returns a function that triggers form submission */
useSubmit: () => () => void;
/** Returns a snapshot of current form values */
useWatch: (names?: string[]) => Record<string, unknown>;
/**
* Optional: trigger validation for specific fields.
* Used by RavenWizardForm to gate step advancement.
*/
useTrigger?: () => (names: string[]) => Promise<boolean>;
}
FieldBinding
interface FieldBinding {
value: unknown;
onChange: (value: unknown) => void;
onBlur: () => void;
error?: string;
isDirty?: boolean;
isTouched?: boolean;
}
Creating a Custom FormAdapter
Use createFormAdapter to build a type-safe adapter:
import {
createFormAdapter,
type FormAdapterProviderProps,
type FieldBinding,
} from "raven-form-engine";
import { useRef, useState, useContext, createContext } from "react";
// 1. Create your own context for form state
const MyFormCtx = createContext(null);
// 2. Build the Provider
const MyProvider = ({
onSubmit,
defaultValues = {},
children,
}: FormAdapterProviderProps) => {
const [values, setValues] = useState(defaultValues);
const [errors, setErrors] = useState({});
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(values);
};
return (
<MyFormCtx.Provider
value={{ values, setValues, errors, setErrors, handleSubmit }}
>
<form onSubmit={handleSubmit}>{children}</form>
</MyFormCtx.Provider>
);
};
// 3. Register with createFormAdapter
export const MyFormAdapter = createFormAdapter({
Provider: MyProvider,
useField: (name) => {
const { values, setValues, errors } = useContext(MyFormCtx);
return {
value: values[name] ?? "",
onChange: (v) => setValues((prev) => ({ ...prev, [name]: v })),
onBlur: () => {},
error: errors[name],
};
},
useSubmit: () => {
const { handleSubmit } = useContext(MyFormCtx);
return handleSubmit;
},
useWatch: (names) => {
const { values } = useContext(MyFormCtx);
if (!names) return values;
return Object.fromEntries(names.map((n) => [n, values[n]]));
},
});
Global vs Per-Form Adapters
Set the default adapter once in your app's layout:
// app/layout.tsx
<RavenFormProvider
formAdapter={RavenRHFAdapter}
uiAdapter={RavenShadcnUIAdapter}
>
{children}
</RavenFormProvider>
Override per-form when needed:
// This form uses a lightweight in-memory adapter instead of RHF
<RavenForm
schema={quickSearchSchema}
onSubmit={handleSearch}
adapter={RavenAntDAdapter}
ui={myMinimalUI}
/>
Per-Adapter Registry (Multi-Adapter)
Register multiple named adapters and reference them by key using RavenAdapterRegistry:
import { RavenFormProvider } from "raven-form-engine";
<RavenFormProvider
registry={{
default: { formAdapter: RavenRHFAdapter, uiAdapter: RavenShadcnUIAdapter },
antd: { formAdapter: RavenAntDAdapter, uiAdapter: RavenAntDUIAdapter },
simple: { formAdapter: RavenAntDAdapter, uiAdapter: myMinimalUI },
}}
>
{children}
</RavenFormProvider>
Adapter Validation Utilities
import { validateFormAdapter } from "raven-form-engine";
// In development, logs warnings for missing required fields
validateFormAdapter(myFormAdapter);