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.

NPM license

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 * 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

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

Name Type
T extends FormbitValues

Type declaration

Name Type
errors Errors
form T
initialValues T
isDirty boolean
liveValidation LiveValidation

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

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

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

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

index.ts:68

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

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

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

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

Name Type
T extends FormbitValues

Type declaration

▸ (writer, setError): void

Parameters
Name Type
writer FormState<T>
setError SetError
Returns

void

Defined in

index.ts:60

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

index.ts:89

Initialize

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

See FormbitObject.initialize.

Type parameters

Name Type
T extends FormbitValues

Type declaration

▸ (values): void

Parameters
Name Type
values Partial<T>
Returns

void

Defined in

index.ts:93

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

index.ts:96

RemoveAll

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

See FormbitObject.removeAll.

Type parameters

Name Type
T extends FormbitValues

Type declaration

▸ (arr, options?): void

Parameters
Name Type
arr string[]
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
Name Type
path string
value string
Returns

void

Defined in

index.ts:99

SetSchema

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

See FormbitObject.setSchema.

Type parameters

Name Type
T extends FormbitValues

Type declaration

▸ (newSchema): void

Parameters
Name Type
newSchema ValidationSchema<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

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

index.ts:132

Validate

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

See FormbitObject.validate.

Type parameters

Name Type
T extends FormbitValues

Type declaration

▸ (path, options?): void

Parameters
Name Type
path string
options? ValidateFnOptions<T>
Returns

void

Defined in

index.ts:120

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

index.ts:123

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

index.ts:126

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

index.ts:108

WriteAll

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

See FormbitObject.writeAll.

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

index.ts:112

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

index.ts:140

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

index.ts:147

WriteAllValue

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

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

Type parameters

Name Type
T extends 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

Name Type
T extends 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

Name Type
T extends FormbitValues

Defined in

index.ts:49

License

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages