Skip to content

Repository files navigation

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.

NPMlicense

Table of contents

Features

  • Intuitive and easy-to-use form state management.
  • Out of the box support for validation with yup.
  • Full TypeScript genericsuseFormbit<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 formbit
yarn add formbit

Getting Started

Three steps: define a schema, call the hook, bind the UI.

import*asyupfrom'yup';importuseFormbitfrom'@radicalbit/formbit';// 1. Define a Yup schema and infer the TypeScript type from itconstschema=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'),});typeFormData=yup.InferType<typeofschema>;constinitialValues: Partial<FormData>={name: undefined,age: undefined};// 2. Call the hook with generics so every callback is fully typedfunctionExample(){const{ form, submitForm, write, error, isDirty }=useFormbit<FormData>({
initialValues,yup: schema,});consthandleChangeName=({target: { value }}: React.ChangeEvent<HTMLInputElement>)=>{write('name',value);};consthandleChangeAge=({target: { value }}: React.ChangeEvent<HTMLInputElement>)=>{write('age',Number(value));};consthandleSubmit=()=>{submitForm(({ form })=>console.log('Validated form:',form),({ errors })=>console.error('Validation errors:',errors),);};// 3. Bind inputs, errors, and submit — Formbit stays out of your UIreturn(<div><labelhtmlFor="name">Name</label><inputid="name"type="text"value={form.name??''}onChange={handleChangeName}/><div>{error('name')}</div><labelhtmlFor="age">Age</label><inputid="age"type="number"value={form.age??''}onChange={handleChangeAge}/><div>{error('age')}</div><buttondisabled={!isDirty}onClick={handleSubmit}type="button">
Submit
</button></div>);}exportdefaultExample;

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*asyupfrom'yup';constschema=yup.object({name: yup.string().required('Name is required'),surname: yup.string().required('Surname is required'),age: yup.number().required('Age is required'),});typeFormData=yup.InferType<typeofschema>;constinitialValues: Partial<FormData>={name: undefined,surname: undefined,age: undefined};// Wrap your form tree with the providerfunctionApp(){return(<FormbitContextProvider<FormData>initialValues={initialValues}yup={schema}><NameField/><SubmitButton/></FormbitContextProvider>);}// Any child can access form state without propsfunctionNameField(){const{ form, write, error }=useFormbitContext<FormData>();consthandleChangeName=({target: { value }}: React.ChangeEvent<HTMLInputElement>)=>{write('name',value);};return(<div><inputvalue={form.name??''}onChange={handleChangeName}/><span>{error('name')}</span></div>);}functionSubmitButton(){const{ submitForm, isDirty }=useFormbitContext<FormData>();return(<buttondisabled={!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';importuseFormbitfrom'@radicalbit/formbit';import*asyupfrom'yup';constschema=yup.object({name: yup.string().required(),email: yup.string().email().required(),});typeFormData=yup.InferType<typeofschema>;constinitialValues: Partial<FormData>={name: undefined,email: undefined};functionEditUserForm({ 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 valuesuseEffect(()=>{fetch(`/api/users/${userId}`).then((res)=>res.json()).then((user)=>{initialize(user);setLoading(false);});},[userId]);consthandleChangeName=({target: { value }}: React.ChangeEvent<HTMLInputElement>)=>{write('name',value);};consthandleChangeEmail=({target: { value }}: React.ChangeEvent<HTMLInputElement>)=>{write('email',value);};consthandleSubmit=(e: React.FormEvent)=>{e.preventDefault();submitForm(({ form })=>console.log(form));};if(loading)return<p>Loading...</p>;return(<formonSubmit={handleSubmit}><inputvalue={form.name??''}onChange={handleChangeName}/><div>{error('name')}</div><inputvalue={form.email??''}onChange={handleChangeEmail}/><div>{error('email')}</div><buttontype="submit">Save</button></form>);}

Multi-Step Form

Use __metadata to store step state and validateAll to gate navigation between steps.

importuseFormbitfrom'@radicalbit/formbit';import*asyupfrom'yup';constschema=yup.object({name: yup.string().required('Name is required'),age: yup.number().required('Age is required'),email: yup.string().email().required('Email is required'),});typeFormData=yup.InferType<typeofschema>;constinitialValues: Partial<FormData>&{__metadata: {step: number}}={name: undefined,age: undefined,email: undefined,__metadata: {step: 0},};functionMultiStepForm(){const{ form, write, error, validateAll, submitForm }=useFormbit<FormData>({
initialValues,yup: schema,});conststep=(form.__metadata?.stepasnumber)??0;constgoTo=(n: number)=>write('__metadata.step',n);// Validate only the current step's fields before advancingconstnext=(paths: string[])=>{validateAll(paths,{successCallback: ()=>goTo(step+1),});};consthandleChangeName=({target: { value }}: React.ChangeEvent<HTMLInputElement>)=>{write('name',value);};consthandleChangeAge=({target: { value }}: React.ChangeEvent<HTMLInputElement>)=>{write('age',Number(value));};consthandleChangeEmail=({target: { value }}: React.ChangeEvent<HTMLInputElement>)=>{write('email',value);};consthandleSubmit=()=>{submitForm(({ form })=>console.log('Submit:',form));};return(<div>{step===0&&(<div><inputvalue={form.name??''}onChange={handleChangeName}/><div>{error('name')}</div><buttononClick={()=>next(['name'])}>Next</button></div>)}{step===1&&(<div><inputtype="number"value={form.age??''}onChange={handleChangeAge}/><div>{error('age')}</div><buttononClick={()=>goTo(0)}>Back</button><buttononClick={()=>next(['age'])}>Next</button></div>)}{step===2&&(<div><inputvalue={form.email??''}onChange={handleChangeEmail}/><div>{error('email')}</div><buttononClick={()=>goTo(1)}>Back</button><buttononClick={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

NameType
Textends FormbitValues

Type declaration

NameTypeDescription
checkCheck<Partial<T>>Validates json against the current schema; returns the errors, or undefined if valid.
error(path: string) => string | undefined-
errorsErrorsError messages registered since the last validation, keyed by the value's dot-path. Examplets form: { age: 1 } errors: { age: "Age must be greater than 18" }
formPartial<T>The current form values. Partial: fields may be missing until validated.
initializeInitialize<T>Re-initializes the form with new initial values.
isDirtybooleanTrue once the user has interacted with the form.
isFormInvalid() => boolean-
isFormValid() => boolean-
liveValidation(path: string) => true | undefined-
removeRemove<T>Removes the value at path, sets isDirty, then validates pathsToValidate plus every live-validated field.
removeAllRemoveAll<T>Removes every given path, sets isDirty, then validates pathsToValidate plus every live-validated field.
resetForm() => void-
setErrorSetErrorSets the error message at path.
setSchemaSetSchema<T>Replaces the current validation schema.
submitFormSubmitForm<T>Validates the whole form and, if valid, runs the success callback to submit.
validateValidate<T>Validates only path (ignores live-validated fields).
validateAllValidateAll<T>Validates only the given paths (ignores live-validated fields).
validateFormValidateForm<Partial<T>>Validates the whole form and registers any error.
writeWrite<T>Writes value at path, sets isDirty, then validates pathsToValidate plus every live-validated field.
writeAllWriteAll<T>Writes every [path, value] pair, sets isDirty, then validates pathsToValidate plus every live-validated field.

Defined in

index.ts:186

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

index.ts:23

FormState

Ƭ FormState<T>: Object

The whole internal state of the form (everything except the validation schema).

Type parameters

NameType
Textends FormbitValues

Type declaration

NameType
errorsErrors
formT
initialValuesT
isDirtyboolean
liveValidationLiveValidation

Defined in

index.ts:38

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

index.ts:13

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

index.ts:33

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

NameType
Textends FormbitValues

Type declaration

▸ (json, inner, writer, setError): void

Parameters
NameType
jsonFormbitValues
innerValidationError[]
writerFormState<T>
setErrorSetError
Returns

void

Defined in

index.ts:72

CheckSuccessCallback

Ƭ CheckSuccessCallback<T>: (json: FormbitValues, writer: FormState<T>, setError: SetError) => void

Invoked by check() when the given json is valid.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (json, writer, setError): void

Parameters
NameType
jsonFormbitValues
writerFormState<T>
setErrorSetError
Returns

void

Defined in

index.ts:68

ErrorCallback

Ƭ ErrorCallback<T>: (writer: FormState<T>, setError: SetError) => void

Invoked by validation methods when validation fails.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (writer, setError): void

Parameters
NameType
writerFormState<T>
setErrorSetError
Returns

void

Defined in

index.ts:64

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

NameType
Textends FormbitValues

Type declaration

▸ (writer, setError, clearIsDirty): void

Parameters
NameType
writerFormState<Omit<T, "__metadata">>
setErrorSetError
clearIsDirty() => void
Returns

void

Defined in

index.ts:79

SuccessCallback

Ƭ SuccessCallback<T>: (writer: FormState<T>, setError: SetError) => void

Invoked by validation methods when the form (or the validated paths) are valid.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (writer, setError): void

Parameters
NameType
writerFormState<T>
setErrorSetError
Returns

void

Defined in

index.ts:60

Method Types

Check

Ƭ Check<T>: (json: FormbitValues, options?: CheckFnOptions<T>) => ValidationError[] | undefined

See FormbitObject.check.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (json, options?): ValidationError[] | undefined

Parameters
NameType
jsonFormbitValues
options?CheckFnOptions<T>
Returns

ValidationError[] | undefined

Defined in

index.ts:89

Initialize

Ƭ Initialize<T>: (values: Partial<T>) => void

See FormbitObject.initialize.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (values): void

Parameters
NameType
valuesPartial<T>
Returns

void

Defined in

index.ts:93

Remove

Ƭ Remove<T>: (path: string, options?: WriteFnOptions<T>) => void

See FormbitObject.remove.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (path, options?): void

Parameters
NameType
pathstring
options?WriteFnOptions<T>
Returns

void

Defined in

index.ts:96

RemoveAll

Ƭ RemoveAll<T>: (arr: string[], options?: WriteFnOptions<T>) => void

See FormbitObject.removeAll.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (arr, options?): void

Parameters
NameType
arrstring[]
options?WriteFnOptions<T>
Returns

void

Defined in

index.ts:116

SetError

Ƭ SetError: (path: string, value: string) => void

See FormbitObject.setError.

Type declaration

▸ (path, value): void

Parameters
NameType
pathstring
valuestring
Returns

void

Defined in

index.ts:99

SetSchema

Ƭ SetSchema<T>: (newSchema: ValidationSchema<T>) => void

See FormbitObject.setSchema.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (newSchema): void

Parameters
NameType
newSchemaValidationSchema<T>
Returns

void

Defined in

index.ts:102

SubmitForm

Ƭ SubmitForm<T>: (successCallback: SubmitSuccessCallback<T>, errorCallback?: ErrorCallback<Partial<T>>, options?: ValidateOptions) => void

See FormbitObject.submitForm.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (successCallback, errorCallback?, options?): void

Parameters
NameType
successCallbackSubmitSuccessCallback<T>
errorCallback?ErrorCallback<Partial<T>>
options?ValidateOptions
Returns

void

Defined in

index.ts:132

Validate

Ƭ Validate<T>: (path: string, options?: ValidateFnOptions<T>) => void

See FormbitObject.validate.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (path, options?): void

Parameters
NameType
pathstring
options?ValidateFnOptions<T>
Returns

void

Defined in

index.ts:120

ValidateAll

Ƭ ValidateAll<T>: (paths: string[], options?: ValidateFnOptions<T>) => void

See FormbitObject.validateAll.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (paths, options?): void

Parameters
NameType
pathsstring[]
options?ValidateFnOptions<T>
Returns

void

Defined in

index.ts:123

ValidateForm

Ƭ ValidateForm<T>: (successCallback?: SuccessCallback<T>, errorCallback?: ErrorCallback<T>, options?: ValidateOptions) => void

See FormbitObject.validateForm.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (successCallback?, errorCallback?, options?): void

Parameters
NameType
successCallback?SuccessCallback<T>
errorCallback?ErrorCallback<T>
options?ValidateOptions
Returns

void

Defined in

index.ts:126

Write

Ƭ Write<T>: (path: keyof T | string, value: unknown, options?: WriteFnOptions<T>) => void

See FormbitObject.write.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (path, value, options?): void

Parameters
NameType
pathkeyof T | string
valueunknown
options?WriteFnOptions<T>
Returns

void

Defined in

index.ts:108

WriteAll

Ƭ WriteAll<T>: (arr: WriteAllValue<T>[], options?: WriteFnOptions<T>) => void

See FormbitObject.writeAll.

Type parameters

NameType
Textends FormbitValues

Type declaration

▸ (arr, options?): void

Parameters
NameType
arrWriteAllValue<T>[]
options?WriteFnOptions<T>
Returns

void

Defined in

index.ts:112

Options Types

CheckFnOptions

Ƭ CheckFnOptions<T>: Object

Options accepted by check().

Type parameters

NameType
Textends FormbitValues

Type declaration

NameType
errorCallback?CheckErrorCallback<T>
options?ValidateOptions
successCallback?CheckSuccessCallback<T>

Defined in

index.ts:140

ValidateFnOptions

Ƭ ValidateFnOptions<T>: Object

Options accepted by the validate methods.

Type parameters

NameType
Textends FormbitValues

Type declaration

NameType
errorCallback?ErrorCallback<Partial<T>>
options?ValidateOptions
successCallback?SuccessCallback<Partial<T>>

Defined in

index.ts:147

WriteAllValue

Ƭ WriteAllValue<T>: [keyof T | string, unknown]

A single [path, value] pair accepted by writeAll.

Type parameters

NameType
Textends FormbitValues

Defined in

index.ts:105

WriteFnOptions

Ƭ WriteFnOptions<T>: { noLiveValidation?: boolean ; pathsToValidate?: string[] } & ValidateFnOptions<T>

Options accepted by the write/remove methods (validate options plus path control).

Type parameters

NameType
Textends FormbitValues

Defined in

index.ts:154

Yup Re-Exports

ValidateOptions

Ƭ ValidateOptions: YupValidateOptions

Options forwarded to yup's validation methods. See https://github.com/jquense/yup.

Defined in

index.ts:52

ValidationError

Ƭ ValidationError: YupValidationError

The error object yup throws when a validation fails. See https://github.com/jquense/yup.

Defined in

index.ts:55

ValidationSchema

Ƭ ValidationSchema<T>: ObjectSchema<T>

A validation schema built with yup.object(). See https://github.com/jquense/yup.

Type parameters

NameType
Textends FormbitValues

Defined in

index.ts:49

License

MIT © [Radicalbit (https://github.com/radicalbit)]

About

No description, website, or topics provided.

Resources

Stars

12 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages