@radicalbit/formbit
v4.0.0
Published
Lightweight state form library
Readme
Formbit
Formbit is a lightweight React state form library designed to simplify form management within your applications. With Formbit, you can easily handle form state, validate user input, and submit data efficiently.
Table of contents
Features
- Intuitive and easy-to-use form state management.
- Out of the box support for validation with yup.
- Full TypeScript generics —
useFormbit<FormData>(...)infers paths, values, and callbacks. - Support for handling complex forms with dynamic and nested fields via dot-path notation.
- Context Provider for sharing form state across deeply nested component trees.
- Seamless and flexible integration with React — works with Antd, MaterialUI, or plain HTML.
Install
npm install --save formbityarn add formbitGetting Started
Three steps: define a schema, call the hook, bind the UI.
import * as yup from 'yup';
import useFormbit from '@radicalbit/formbit';
// 1. Define a Yup schema and infer the TypeScript type from it
const schema = yup.object({
name: yup.string().max(25, 'Max 25 characters').required('Name is required'),
age: yup.number().max(120, 'Must be 0–120').required('Age is required'),
});
type FormData = yup.InferType<typeof schema>;
const initialValues: Partial<FormData> = { name: undefined, age: undefined };
// 2. Call the hook with generics so every callback is fully typed
function Example() {
const { form, submitForm, write, error, isDirty } = useFormbit<FormData>({
initialValues,
yup: schema,
});
const handleChangeName = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
write('name', value);
};
const handleChangeAge = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
write('age', Number(value));
};
const handleSubmit = () => {
submitForm(
({ form }) => console.log('Validated form:', form),
({ errors }) => console.error('Validation errors:', errors),
);
};
// 3. Bind inputs, errors, and submit — Formbit stays out of your UI
return (
<div>
<label htmlFor="name">Name</label>
<input
id="name"
type="text"
value={form.name ?? ''}
onChange={handleChangeName}
/>
<div>{error('name')}</div>
<label htmlFor="age">Age</label>
<input
id="age"
type="number"
value={form.age ?? ''}
onChange={handleChangeAge}
/>
<div>{error('age')}</div>
<button disabled={!isDirty} onClick={handleSubmit} type="button">
Submit
</button>
</div>
);
}
export default Example;Usage Patterns
Context Provider
Use FormbitContextProvider when you need to share form state across deeply nested components without prop drilling.
import { FormbitContextProvider, useFormbitContext } from '@radicalbit/formbit';
import * as yup from 'yup';
const schema = yup.object({
name: yup.string().required('Name is required'),
surname: yup.string().required('Surname is required'),
age: yup.number().required('Age is required'),
});
type FormData = yup.InferType<typeof schema>;
const initialValues: Partial<FormData> = { name: undefined, surname: undefined, age: undefined };
// Wrap your form tree with the provider
function App() {
return (
<FormbitContextProvider<FormData>
initialValues={initialValues}
yup={schema}
>
<NameField />
<SubmitButton />
</FormbitContextProvider>
);
}
// Any child can access form state without props
function NameField() {
const { form, write, error } = useFormbitContext<FormData>();
const handleChangeName = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
write('name', value);
};
return (
<div>
<input
value={form.name ?? ''}
onChange={handleChangeName}
/>
<span>{error('name')}</span>
</div>
);
}
function SubmitButton() {
const { submitForm, isDirty } = useFormbitContext<FormData>();
return (
<button
disabled={!isDirty}
onClick={() => submitForm(({ form }) => console.log(form))}
>
Submit
</button>
);
}Edit / Initialize Pattern
Start with empty initial values and call initialize() once data arrives from an API.
import { useEffect, useState } from 'react';
import useFormbit from '@radicalbit/formbit';
import * as yup from 'yup';
const schema = yup.object({
name: yup.string().required(),
email: yup.string().email().required(),
});
type FormData = yup.InferType<typeof schema>;
const initialValues: Partial<FormData> = { name: undefined, email: undefined };
function EditUserForm({ userId }: { userId: string }) {
const { form, write, error, initialize, submitForm } = useFormbit<FormData>({
initialValues,
yup: schema,
});
const [loading, setLoading] = useState(true);
// Fetch and initialize — resetForm() will revert to these values
useEffect(() => {
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((user) => { initialize(user); setLoading(false); });
}, [userId]);
const handleChangeName = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
write('name', value);
};
const handleChangeEmail = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
write('email', value);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
submitForm(({ form }) => console.log(form));
};
if (loading) return <p>Loading...</p>;
return (
<form onSubmit={handleSubmit}>
<input value={form.name ?? ''} onChange={handleChangeName} />
<div>{error('name')}</div>
<input value={form.email ?? ''} onChange={handleChangeEmail} />
<div>{error('email')}</div>
<button type="submit">Save</button>
</form>
);
}Multi-Step Form
Use __metadata to store step state and validateAll to gate navigation between steps.
import useFormbit from '@radicalbit/formbit';
import * as yup from 'yup';
const schema = yup.object({
name: yup.string().required('Name is required'),
age: yup.number().required('Age is required'),
email: yup.string().email().required('Email is required'),
});
type FormData = yup.InferType<typeof schema>;
const initialValues: Partial<FormData> & { __metadata: { step: number } } = {
name: undefined,
age: undefined,
email: undefined,
__metadata: { step: 0 },
};
function MultiStepForm() {
const { form, write, error, validateAll, submitForm } = useFormbit<FormData>({
initialValues,
yup: schema,
});
const step = (form.__metadata?.step as number) ?? 0;
const goTo = (n: number) => write('__metadata.step', n);
// Validate only the current step's fields before advancing
const next = (paths: string[]) => {
validateAll(paths, {
successCallback: () => goTo(step + 1),
});
};
const handleChangeName = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
write('name', value);
};
const handleChangeAge = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
write('age', Number(value));
};
const handleChangeEmail = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>) => {
write('email', value);
};
const handleSubmit = () => {
submitForm(({ form }) => console.log('Submit:', form));
};
return (
<div>
{step === 0 && (
<div>
<input value={form.name ?? ''} onChange={handleChangeName} />
<div>{error('name')}</div>
<button onClick={() => next(['name'])}>Next</button>
</div>
)}
{step === 1 && (
<div>
<input type="number" value={form.age ?? ''} onChange={handleChangeAge} />
<div>{error('age')}</div>
<button onClick={() => goTo(0)}>Back</button>
<button onClick={() => next(['age'])}>Next</button>
</div>
)}
{step === 2 && (
<div>
<input value={form.email ?? ''} onChange={handleChangeEmail} />
<div>{error('email')}</div>
<button onClick={() => goTo(1)}>Back</button>
<button onClick={handleSubmit}>Submit</button>
</div>
)}
</div>
);
}Local Development
For local development we suggest using Yalc to test your local version of formbit in your projects.
API Reference
FormbitObject
Ƭ FormbitObject<T>: Object
The object returned by useFormbit() and useFormbitContext(). Holds the form
state and every method needed to read, mutate and validate the form.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
| Name | Type | Description |
| :------ | :------ | :------ |
| check | Check<Partial<T>> | Validates json against the current schema; returns the errors, or undefined if valid. |
| error | (path: string) => string | undefined | - |
| errors | Errors | Error messages registered since the last validation, keyed by the value's dot-path. Example ts form: { age: 1 } errors: { age: "Age must be greater than 18" } |
| form | Partial<T> | The current form values. Partial: fields may be missing until validated. |
| initialize | Initialize<T> | Re-initializes the form with new initial values. |
| isDirty | boolean | True once the user has interacted with the form. |
| isFormInvalid | () => boolean | - |
| isFormValid | () => boolean | - |
| liveValidation | (path: string) => true | undefined | - |
| remove | Remove<T> | Removes the value at path, sets isDirty, then validates pathsToValidate plus every live-validated field. |
| removeAll | RemoveAll<T> | Removes every given path, sets isDirty, then validates pathsToValidate plus every live-validated field. |
| resetForm | () => void | - |
| setError | SetError | Sets the error message at path. |
| setSchema | SetSchema<T> | Replaces the current validation schema. |
| submitForm | SubmitForm<T> | Validates the whole form and, if valid, runs the success callback to submit. |
| validate | Validate<T> | Validates only path (ignores live-validated fields). |
| validateAll | ValidateAll<T> | Validates only the given paths (ignores live-validated fields). |
| validateForm | ValidateForm<Partial<T>> | Validates the whole form and registers any error. |
| write | Write<T> | Writes value at path, sets isDirty, then validates pathsToValidate plus every live-validated field. |
| writeAll | WriteAll<T> | Writes every [path, value] pair, sets isDirty, then validates pathsToValidate plus every live-validated field. |
Defined in
Core Types
Errors
Ƭ Errors: Record<string, string>
Error messages registered since the last validation, stored under the same dot-path as the corresponding form value.
Example
form: { age: 1 }
errors: { age: "Age must be greater than 18" }Defined in
FormState
Ƭ FormState<T>: Object
The whole internal state of the form (everything except the validation schema).
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
| Name | Type |
| :------ | :------ |
| errors | Errors |
| form | T |
| initialValues | T |
| isDirty | boolean |
| liveValidation | LiveValidation |
Defined in
FormbitValues
Ƭ FormbitValues: Record<string, unknown> & { __metadata?: Record<string, unknown> }
Base shape of every form handled by formbit: an open record of values, plus an
optional __metadata field formbit uses to carry data that must survive a
reset/initialize but must NOT be submitted.
The generic T you pass to useFormbit<T>() must extend this type.
Defined in
LiveValidation
Ƭ LiveValidation: Record<string, true>
Fields currently under live-validation (re-validated on every form change). A field is added here automatically when it fails a validation. Empty by default.
Example
form: { age: 1 }
liveValidation: { age: true }Defined in
Callback Types
CheckErrorCallback
Ƭ CheckErrorCallback<T>: (json: FormbitValues, inner: ValidationError[], writer: FormState<T>, setError: SetError) => void
Invoked by check() when the given json is invalid.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (json, inner, writer, setError): void
Parameters
| Name | Type |
| :------ | :------ |
| json | FormbitValues |
| inner | ValidationError[] |
| writer | FormState<T> |
| setError | SetError |
Returns
void
Defined in
CheckSuccessCallback
Ƭ CheckSuccessCallback<T>: (json: FormbitValues, writer: FormState<T>, setError: SetError) => void
Invoked by check() when the given json is valid.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (json, writer, setError): void
Parameters
| Name | Type |
| :------ | :------ |
| json | FormbitValues |
| writer | FormState<T> |
| setError | SetError |
Returns
void
Defined in
ErrorCallback
Ƭ ErrorCallback<T>: (writer: FormState<T>, setError: SetError) => void
Invoked by validation methods when validation fails.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (writer, setError): void
Parameters
| Name | Type |
| :------ | :------ |
| writer | FormState<T> |
| setError | SetError |
Returns
void
Defined in
SubmitSuccessCallback
Ƭ SubmitSuccessCallback<T>: (writer: FormState<Omit<T, "__metadata">>, setError: SetError, clearIsDirty: () => void) => void
Invoked by submitForm() once the whole form is valid — the place to send data
to the backend. __metadata is stripped from writer.form before this runs.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (writer, setError, clearIsDirty): void
Parameters
| Name | Type |
| :------ | :------ |
| writer | FormState<Omit<T, "__metadata">> |
| setError | SetError |
| clearIsDirty | () => void |
Returns
void
Defined in
SuccessCallback
Ƭ SuccessCallback<T>: (writer: FormState<T>, setError: SetError) => void
Invoked by validation methods when the form (or the validated paths) are valid.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (writer, setError): void
Parameters
| Name | Type |
| :------ | :------ |
| writer | FormState<T> |
| setError | SetError |
Returns
void
Defined in
Method Types
Check
Ƭ Check<T>: (json: FormbitValues, options?: CheckFnOptions<T>) => ValidationError[] | undefined
See FormbitObject.check.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (json, options?): ValidationError[] | undefined
Parameters
| Name | Type |
| :------ | :------ |
| json | FormbitValues |
| options? | CheckFnOptions<T> |
Returns
ValidationError[] | undefined
Defined in
Initialize
Ƭ Initialize<T>: (values: Partial<T>) => void
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (values): void
Parameters
| Name | Type |
| :------ | :------ |
| values | Partial<T> |
Returns
void
Defined in
Remove
Ƭ Remove<T>: (path: string, options?: WriteFnOptions<T>) => void
See FormbitObject.remove.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (path, options?): void
Parameters
| Name | Type |
| :------ | :------ |
| path | string |
| options? | WriteFnOptions<T> |
Returns
void
Defined in
RemoveAll
Ƭ RemoveAll<T>: (arr: string[], options?: WriteFnOptions<T>) => void
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (arr, options?): void
Parameters
| Name | Type |
| :------ | :------ |
| arr | string[] |
| options? | WriteFnOptions<T> |
Returns
void
Defined in
SetError
Ƭ SetError: (path: string, value: string) => void
Type declaration
▸ (path, value): void
Parameters
| Name | Type |
| :------ | :------ |
| path | string |
| value | string |
Returns
void
Defined in
SetSchema
Ƭ SetSchema<T>: (newSchema: ValidationSchema<T>) => void
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (newSchema): void
Parameters
| Name | Type |
| :------ | :------ |
| newSchema | ValidationSchema<T> |
Returns
void
Defined in
SubmitForm
Ƭ SubmitForm<T>: (successCallback: SubmitSuccessCallback<T>, errorCallback?: ErrorCallback<Partial<T>>, options?: ValidateOptions) => void
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (successCallback, errorCallback?, options?): void
Parameters
| Name | Type |
| :------ | :------ |
| successCallback | SubmitSuccessCallback<T> |
| errorCallback? | ErrorCallback<Partial<T>> |
| options? | ValidateOptions |
Returns
void
Defined in
Validate
Ƭ Validate<T>: (path: string, options?: ValidateFnOptions<T>) => void
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (path, options?): void
Parameters
| Name | Type |
| :------ | :------ |
| path | string |
| options? | ValidateFnOptions<T> |
Returns
void
Defined in
ValidateAll
Ƭ ValidateAll<T>: (paths: string[], options?: ValidateFnOptions<T>) => void
See FormbitObject.validateAll.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (paths, options?): void
Parameters
| Name | Type |
| :------ | :------ |
| paths | string[] |
| options? | ValidateFnOptions<T> |
Returns
void
Defined in
ValidateForm
Ƭ ValidateForm<T>: (successCallback?: SuccessCallback<T>, errorCallback?: ErrorCallback<T>, options?: ValidateOptions) => void
See FormbitObject.validateForm.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (successCallback?, errorCallback?, options?): void
Parameters
| Name | Type |
| :------ | :------ |
| successCallback? | SuccessCallback<T> |
| errorCallback? | ErrorCallback<T> |
| options? | ValidateOptions |
Returns
void
Defined in
Write
Ƭ Write<T>: (path: keyof T | string, value: unknown, options?: WriteFnOptions<T>) => void
See FormbitObject.write.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (path, value, options?): void
Parameters
| Name | Type |
| :------ | :------ |
| path | keyof T | string |
| value | unknown |
| options? | WriteFnOptions<T> |
Returns
void
Defined in
WriteAll
Ƭ WriteAll<T>: (arr: WriteAllValue<T>[], options?: WriteFnOptions<T>) => void
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
▸ (arr, options?): void
Parameters
| Name | Type |
| :------ | :------ |
| arr | WriteAllValue<T>[] |
| options? | WriteFnOptions<T> |
Returns
void
Defined in
Options Types
CheckFnOptions
Ƭ CheckFnOptions<T>: Object
Options accepted by check().
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
| Name | Type |
| :------ | :------ |
| errorCallback? | CheckErrorCallback<T> |
| options? | ValidateOptions |
| successCallback? | CheckSuccessCallback<T> |
Defined in
ValidateFnOptions
Ƭ ValidateFnOptions<T>: Object
Options accepted by the validate methods.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Type declaration
| Name | Type |
| :------ | :------ |
| errorCallback? | ErrorCallback<Partial<T>> |
| options? | ValidateOptions |
| successCallback? | SuccessCallback<Partial<T>> |
Defined in
WriteAllValue
Ƭ WriteAllValue<T>: [keyof T | string, unknown]
A single [path, value] pair accepted by writeAll.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Defined in
WriteFnOptions
Ƭ WriteFnOptions<T>: { noLiveValidation?: boolean ; pathsToValidate?: string[] } & ValidateFnOptions<T>
Options accepted by the write/remove methods (validate options plus path control).
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Defined in
Yup Re-Exports
ValidateOptions
Ƭ ValidateOptions: YupValidateOptions
Options forwarded to yup's validation methods. See https://github.com/jquense/yup.
Defined in
ValidationError
Ƭ ValidationError: YupValidationError
The error object yup throws when a validation fails. See https://github.com/jquense/yup.
Defined in
ValidationSchema
Ƭ ValidationSchema<T>: ObjectSchema<T>
A validation schema built with yup.object(). See https://github.com/jquense/yup.
Type parameters
| Name | Type |
| :------ | :------ |
| T | extends FormbitValues |
Defined in
License
MIT © [Radicalbit (https://github.com/radicalbit)]
