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.
- 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.
npm install --save formbityarn add formbitThree 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;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>
);
}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>
);
}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>
);
}For local development we suggest using Yalc to test your local version of formbit in your projects.
Ƭ FormbitObject<Values>: Object
Object returned by useFormbit() and useFormbitContextHook(). It contains all the data and methods needed to handle the form.
| Name | Type |
|---|---|
Values |
extends InitialValues |
| Name | Type | Description |
|---|---|---|
check |
Check<Partial<Values>> |
Checks the given json against the form schema and returns an array of errors. It returns undefined if the json is valid. |
error |
(path: string) => string | undefined |
- |
errors |
Errors |
Object including all the registered error messages since the last validation. Errors are stored using the same path of the corresponding form values. Example If the form object has this structure: json { "age": 1 } and age is a non valid field, errors object will look like this json { "age": "Age must be greater then 18" } |
form |
Partial<Values> |
Object containing the updated form. |
initialize |
Initialize<Values> |
Initialize the form with new initial values. |
isDirty |
boolean |
Returns true if the form is Dirty (user already interacted with the form), false otherwise. |
isFormInvalid |
() => boolean |
- |
isFormValid |
() => boolean |
- |
liveValidation |
(path: string) => true | undefined |
- |
remove |
Remove<Values> |
This method updates the form state deleting value, setting isDirty to true. After writing, it validates all the paths contained into pathsToValidate (if any) and all the fields that have the live validation active. |
removeAll |
RemoveAll<Values> |
This method updates the form state deleting multiple values, setting isDirty to true. |
resetForm |
() => void |
- |
setError |
SetError |
Set a message (value) to the given error path. |
setSchema |
SetSchema<Values> |
Override the current schema with the given one. |
submitForm |
SubmitForm<Values> |
Perform a validation against the current form object, and execute the successCallback if the validation passes, otherwise it executes the errorCallback. |
validate |
Validate<Values> |
This method only validates the specified path. Does not check for fields that have the live validation active. |
validateAll |
ValidateAll<Values> |
This method only validates the specified paths. Does not check for fields that have the live validation active. |
validateForm |
ValidateForm<Partial<Values>> |
This method validates the entire form and sets the corresponding errors if any. |
write |
Write<Values> |
This method updates the form state writing $value into the $path, setting isDirty to true. After writing, it validates all the paths contained into $pathsToValidate (if any) and all the fields that have the live validation active. |
writeAll |
WriteAll<Values> |
This method takes an array of [path, value] and updates the form state writing all those values into the specified paths. It sets isDirty to true. After writing, it validates all the paths contained into $pathToValidate and all the fields that have the live validation active. |
Ƭ Errors: Record<string, string>
Object including all the registered error messages since the last validation. Errors are stored using the same path of the corresponding form values.
Example
If the form object has this structure:
{
"age": 1
}and age is a non valid field, errors object will look like this
{
"age": "Age must be greater then 18"
}Ƭ Form: FormbitValues
Object containing the updated form.
Ƭ FormState<Values>: Object
Internal form state storing all the data of the form (except the validation schema).
| Name | Type |
|---|---|
Values |
extends InitialValues |
| Name | Type |
|---|---|
errors |
Errors |
form |
Values |
initialValues |
Values |
isDirty |
boolean |
liveValidation |
LiveValidation |
Ƭ FormbitValues: { __metadata?: FormbitRecord } & FormbitRecord
Base type for form values: a record of string keys with an optional __metadata field.
Ƭ InitialValues: FormbitValues
InitialValues used to set up formbit; also used to reset the form to its original version.
Ƭ LiveValidation: Record<string, true>
Object including all the values that are being live validated. Usually fields that fail validation (using one of the methods that triggers validation) will automatically be set to live-validated.
A value/path is live-validated when validated at every change of the form.
By default no field is live-validated.
Example
If the form object has this structure:
{
"age": 1
}and age is a field that is being live-validated, liveValidation object will look like this
{
"age": true
}Ƭ CheckErrorCallback<Values>: (json: Form, inner: ValidationError[], writer: FormState<Values>, setError: SetError) => void
Invoked in case of errors raised by validation of check method.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (json, inner, writer, setError): void
| Name | Type |
|---|---|
json |
Form |
inner |
ValidationError[] |
writer |
FormState<Values> |
setError |
SetError |
void
Ƭ CheckSuccessCallback<Values>: (json: Form, writer: FormState<Values>, setError: SetError) => void
Success callback invoked by the check method when the operation is successful.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (json, writer, setError): void
| Name | Type |
|---|---|
json |
Form |
writer |
FormState<Values> |
setError |
SetError |
void
Ƭ ErrorCallback<Values>: (writer: FormState<Values>, setError: SetError) => void
Invoked in case of errors raised by validation.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (writer, setError): void
| Name | Type |
|---|---|
writer |
FormState<Values> |
setError |
SetError |
void
Ƭ SubmitSuccessCallback<Values>: (writer: FormState<Values | Omit<Values, "__metadata">>, setError: SetError, clearIsDirty: () => void) => void
Success callback invoked by the submit method when the validation is successful. Is the right place to send your data to the backend.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (writer, setError, clearIsDirty): void
| Name | Type |
|---|---|
writer |
FormState<Values | Omit<Values, "__metadata">> |
setError |
SetError |
clearIsDirty |
() => void |
void
Ƭ SuccessCallback<Values>: (writer: FormState<Values>, setError: SetError) => void
Success callback invoked by some formbit methods when the operation is successful.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (writer, setError): void
| Name | Type |
|---|---|
writer |
FormState<Values> |
setError |
SetError |
void
Ƭ Check<Values>: (json: Form, options?: CheckFnOptions<Values>) => ValidationError[] | undefined
See FormbitObject.check.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (json, options?): ValidationError[] | undefined
| Name | Type |
|---|---|
json |
Form |
options? |
CheckFnOptions<Values> |
ValidationError[] | undefined
Ƭ Initialize<Values>: (values: Partial<Values>) => void
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (values): void
| Name | Type |
|---|---|
values |
Partial<Values> |
void
Ƭ Remove<Values>: (path: string, options?: WriteFnOptions<Values>) => void
See FormbitObject.remove.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (path, options?): void
| Name | Type |
|---|---|
path |
string |
options? |
WriteFnOptions<Values> |
void
Ƭ RemoveAll<Values>: (arr: string[], options?: WriteFnOptions<Values>) => void
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (arr, options?): void
| Name | Type |
|---|---|
arr |
string[] |
options? |
WriteFnOptions<Values> |
void
Ƭ SetError: (path: string, value: string) => void
▸ (path, value): void
| Name | Type |
|---|---|
path |
string |
value |
string |
void
Ƭ SetSchema<Values>: (newSchema: ValidationSchema<Values>) => void
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (newSchema): void
| Name | Type |
|---|---|
newSchema |
ValidationSchema<Values> |
void
Ƭ SubmitForm<Values>: (successCallback: SubmitSuccessCallback<Values>, errorCallback?: ErrorCallback<Partial<Values>>, options?: ValidateOptions) => void
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (successCallback, errorCallback?, options?): void
| Name | Type |
|---|---|
successCallback |
SubmitSuccessCallback<Values> |
errorCallback? |
ErrorCallback<Partial<Values>> |
options? |
ValidateOptions |
void
Ƭ Validate<Values>: (path: string, options?: ValidateFnOptions<Values>) => void
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (path, options?): void
| Name | Type |
|---|---|
path |
string |
options? |
ValidateFnOptions<Values> |
void
Ƭ ValidateAll<Values>: (paths: string[], options?: ValidateFnOptions<Values>) => void
See FormbitObject.validateAll.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (paths, options?): void
| Name | Type |
|---|---|
paths |
string[] |
options? |
ValidateFnOptions<Values> |
void
Ƭ ValidateForm<Values>: (successCallback?: SuccessCallback<Values>, errorCallback?: ErrorCallback<Values>, options?: ValidateOptions) => void
See FormbitObject.validateForm.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (successCallback?, errorCallback?, options?): void
| Name | Type |
|---|---|
successCallback? |
SuccessCallback<Values> |
errorCallback? |
ErrorCallback<Values> |
options? |
ValidateOptions |
void
Ƭ Write<Values>: (path: keyof Values | string, value: unknown, options?: WriteFnOptions<Values>) => void
See FormbitObject.write.
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (path, value, options?): void
| Name | Type |
|---|---|
path |
keyof Values | string |
value |
unknown |
options? |
WriteFnOptions<Values> |
void
Ƭ WriteAll<Values>: (arr: WriteAllValue<Values>[], options?: WriteFnOptions<Values>) => void
| Name | Type |
|---|---|
Values |
extends InitialValues |
▸ (arr, options?): void
| Name | Type |
|---|---|
arr |
WriteAllValue<Values>[] |
options? |
WriteFnOptions<Values> |
void
Ƭ CheckFnOptions<Values>: Object
Options object to change the behavior of the check method.
| Name | Type |
|---|---|
Values |
extends InitialValues |
| Name | Type |
|---|---|
errorCallback? |
CheckErrorCallback<Values> |
options? |
ValidateOptions |
successCallback? |
CheckSuccessCallback<Values> |
Ƭ ValidateFnOptions<Values>: Object
Options object to change the behavior of the validate methods.
| Name | Type |
|---|---|
Values |
extends InitialValues |
| Name | Type |
|---|---|
errorCallback? |
ErrorCallback<Partial<Values>> |
options? |
ValidateOptions |
successCallback? |
SuccessCallback<Partial<Values>> |
Ƭ WriteAllValue<Values>: [keyof Values | string, unknown]
Tuple of [key, value] pair.
| Name | Type |
|---|---|
Values |
extends InitialValues |
Ƭ WriteFnOptions<Values>: { noLiveValidation?: boolean ; pathsToValidate?: string[] } & ValidateFnOptions<Values>
Options object to change the behavior of the write methods.
| Name | Type |
|---|---|
Values |
extends InitialValues |
Ƭ ValidateOptions: YupValidateOptions
Type imported from the yup library. It represents the object with all the options that can be passed to the internal yup validation method.
Link to the Yup documentation https://github.com/jquense/yup
Ƭ ValidationError: YupValidationError
Type imported from the yup library. It represents the error object returned when a validation fails.
Link to the Yup documentation https://github.com/jquense/yup
Ƭ ValidationSchema<Values>: ObjectSchema<Values>
Type imported from the yup library. It represents any validation schema created with the yup.object() method.
Link to the Yup documentation https://github.com/jquense/yup
| Name | Type |
|---|---|
Values |
extends InitialValues |
Show deprecated types
Ƭ ClearIsDirty: () => void
Deprecated
Inlined into FormbitObject.
▸ (): void
void
Ƭ ErrorCheckCallback<Values>: CheckErrorCallback<Values>
Deprecated
Use CheckErrorCallback instead.
| Name | Type |
|---|---|
Values |
extends InitialValues |
Ƭ ErrorFn: (path: string) => string | undefined
Deprecated
Inlined into FormbitObject.
▸ (path): string | undefined
| Name | Type |
|---|---|
path |
string |
string | undefined
Ƭ IsDirty: boolean
Deprecated
Inlined into FormbitObject.
Ƭ IsFormInvalid: () => boolean
Deprecated
Inlined into FormbitObject.
▸ (): boolean
boolean
Ƭ IsFormValid: () => boolean
Deprecated
Inlined into FormbitObject.
▸ (): boolean
boolean
Ƭ LiveValidationFn: (path: string) => true | undefined
Deprecated
Inlined into FormbitObject.
▸ (path): true | undefined
| Name | Type |
|---|---|
path |
string |
true | undefined
Ƭ Object: FormbitRecord
Deprecated
Use FormbitRecord instead. Renamed to avoid shadowing the global Object.
Ƭ ResetForm: () => void
Deprecated
Inlined into FormbitObject.
▸ (): void
void
Ƭ SuccessCheckCallback<Values>: CheckSuccessCallback<Values>
Deprecated
Use CheckSuccessCallback instead.
| Name | Type |
|---|---|
Values |
extends InitialValues |
Ƭ Writer<Values>: FormState<Values>
Deprecated
Use FormState instead.
| Name | Type |
|---|---|
Values |
extends InitialValues |
MIT © [Radicalbit (https://github.com/radicalbit)]