Socket
Socket
Sign inDemoInstall

formicary

Package Overview
Dependencies
1
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

formicary

Build react forms with reactive validations between forms and a composable API.


Version published
Maintainers
1
Install size
214 kB
Created

Readme

Source

Formicary

Build react forms with reactive validations between forms and a composable API.

Experimental

Usage

import {
  useForm,
  useInput,
  pipeValidators,
  isNumber,
  isAtMost,
  isRequired,
  errors,
} from 'formicary';

const Form = () => {
  const form = useForm<{
    min: number;
    max: number;
  }>();
  const errors = useErrors(form);

  const minRef = useInput(form, 'min', {
    initialValue: 0,
    validator: pipeValidators(isRequired, isNumber, isAtMost('max')),
  });
  const maxRef = useInput(form, 'max', {
    initialValue: 0,
    validator: pipeValidators(isRequired, isNumber),
  });

  return (
    <div>
      <input placeholder="min" ref={minRef} />
      <input placeholder="max" ref={maxRef} />
      <div>{Object.keys(errors).join(' ')}</div>
    </div>
  );
};

Primitive Example: CodeSandbox

Motivation

The main purpose of this library is to provide an easy way of defining validations for every field, specially when they depend on others (e.g. a max quantity field must be greater than a min quantity field).

It's also built in a way that makes breaking down a big form into small pieces easy and optimal.

Because this library is thought in reactivity in mind, it just makes the recomputations necessary when changing values on fields:

  • It doesn't use react's state to track the values of the fields. It treats them as uncontrolled, so that on every keystroke it doesn't generate a re-render.
  • Makes it easy to split a big form into small sections.
  • It only uses react's state where it's needed. Only the components that are subscribed to the specified parts of the model will update.
  • Field-level validators will evaluate only when their value changes, or one of its dependencies change.

API

useForm

// ... in a function component
const form = useForm<ModelType>();

Entry point of this library. By default, it takes no parameters, but the FormModel as a generic type when using typescript. You can also pass in the initial value if needed.

Returns an object needed to use the rest of the utilities of this library. It can be shared with children components.

The properties of this object are internal and shouldn't be used. Please, use some of the provided utilities or raise a new feature request.

Validators

A validator is a function bound to a field, that will evaluate when the value of any of its dependencies changes. It has the following signature:

type Validator<T> = (
  value: T,
  getValue: (key: KeySelector<T>) => any
) => boolean | string[] | Promise<boolean | string[]>;

Formicary currently exposes some common validators to make things less verbose, but it's fairly simple to build your own validator:

  • value: the latest value of the field.
  • getValue: Function to retrieve the latest value of a dependant field. This lets this library wire up all the dependencies.

A validator must return either a boolean (true means validation passes, false means it fails) or a list of error messages. It can also be async, in case it needs some asynchronous logic.

Validators are composable. This library exposes some utilities to compose them easily:

  • pipeValidators(...validators) takes any number of validators and runs them one-by-one, bailing out when one of them reports an error.
  • mergeValidators(...validators) takes any number of validators and runs them simultaneously, returning the list of all errors reported if there were any.
  • conditionalValidator(conditionFn, validator) only runs validator when conditionFn returns true, otherwise it always accepts.

And it provides a set of useful built-in validators:

  • isNumber: Value can be parsed as a number. Correct ['0','12.123'], Incorrect ['abc', '12a']
  • isInteger: Value doesn't have decimals. Correct: ['1', '1.00'], Incorrect: ['1.12', '0.1']
  • isRequired: Value is not empty. Incorrect: ['']
  • isAtLeast(valueOrRef): Value is >= than valueOrRef.
  • isGreaterThan(valueOrRef): Value is > than valueOrRef.
  • isAtMost(valueOrRef): Value is <= than valueOrRef.
  • isLessThan(valueOrRef): Value is < than valueOrRef.
  • matches(RegExp): Value matches RegExp

In some these built-in validators, valueOrRef can either take directly a static value or reference another field by passing in the key of the one that it depends on.

KeySelectors

In many places throughout this library, you need to specify a key to select one field - This key is the stringified version of the path through the model. For example, if our form model is:

interface FormModel {
  interval: {
    min: number;
    max: number;
  };
  quantity: number;
}

Then the key selector for min field is "interval.min", and for quantity "quantity".

In these places, you can also pass in a function instead which usually makes things easier if you're using Typescript. The function-version of the example above would be model => model.interval.min and model => model.quantity respectively.

Keep in mind that these functions are a replacement of a stringified key, so you can only access one property and return it immediately. In reality, model is not the object itself, but a Proxy designed to capture the path that's being returned from the function.

Some of the utilities takes in an array of keys instead. In those cases, this can be supplied either with a simple array: ['quantity', 'interval.min'] or, again, as a function: model => [model.quantity, model.interval.min]

Form Utilities

useInput
const inputRef = useInput(form, key, {
  initialValue?: string | boolean;
  validator?: Validator<T, TValues>;
  elementProp?: string;
  eventType?: 'input' | 'onChange';
})

<input ref={inputRef} />

Registers an input, and returns a ref to be passed into an input-like element: It will add the required event listeners and synchronize the model with its value.

Parameters:

  • key: [Mandatory] model key to bind this input to.
  • initialValue: Initial value to set to this field. Defaults to ''.
  • validator: optional validator to use for this input.
  • elementProp: element's value property. Defaults to value but can be set to checked for checkboxes.
  • eventType: event to listen for changes. Defaults to input.
useControl
const {
  setValue: (value: T) => void,
  subscribe: (cb: (value: T) => void) => () => void,
  touch: () => void,
} = useControl(form, key, {
  initialValue?: T;
  validator?: Validator<T>;
})

For custom form components that don't use a simple input-like element, this is more versatile than useInput. Returns:

  • setValue: Function to set the value of this field in the form model.
  • subscribe: Function that will callback whenever the form model changes. Returns a function that needs to be called to unsubscribe (designed to work easily within useEffect).
  • touch: Marks the field as touched. Usually bound to "onBlur" or similar.

Parameters:

  • key: model key to bind this field to.
  • initialValue: Initial value to set to this field.
  • validator: optional validator to use for this field.
useIsValid
const isValid: boolean | 'pending' = useIsValid(form);

Returns whether the Form Model is valid according to the validation rules of all fields, or 'pending' if any of the fields' validation is async and hasn't resolved yet.

Can take an optional parameter keysSelector to restrict the check to the specified keys.

useErrors
const errors: {
  [key]: string[] | 'pending';
} = useErrors(form);

Returns a keyed object containing all errors by stringified key. These errors will show up per each field only if that field is marked as touched (as usually you only want to show errors after the user has gone through that field).

Can take an optional parameter keysSelector to restrict the check to the specified keys.

touchFields
const handleSubmit = () => {
  if (!isValid) {
    return touchFields(form);
  }
};

Marks all fields in the form as touched. Useful if you don't disable the submit button when invalid and want to highlight the errors instead when the user presses on it.

Can take an optional parameter keysSelector to restrict the effect to the specified keys.

readForm
const handleSubmit = () => {
  const model = readForm(form);
};

Returns the latest value of the form model.

useWatch
const value = useWatch(form, keySelector);

Binds to the field specified by keySelector, so that it will update the value whenever it changes (causing a re-render)

useIsPristine
const isPristine: boolean = useIsPristine(form);

Returns whether all the fields in the form have the same value as their initial value.

resetForm
resetForm(form);

Resets all the fields in the form to their initial value.

Can take an optional parameter keysSelector to restrict the effect to the specified keys.

setFieldValue
setFieldValue(form, keySelector, value);

Sets the value of the specified field.

FAQs

Last updated on 11 Jun 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc