npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

reactive-rx-form

v0.1.1

Published

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

Downloads

9

Readme

Reactive Rx Form

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

Experimental

Usage

import {
  useForm,
  useInput,
  pipeValidators,
  isNumber,
  isAtMost,
  isRequired,
  errors,
} from 'reactive-rx-form';

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

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

  return (
    <div>
      <input name="min" placeholder="min" ref={minRef} />
      <input name="max" 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. Takes no parameters, but the FormModel as a generic type when using typescript.

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[]>;

Reactive Rx Form 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 two 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.

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?: KeySelector<TValues, T>;
  initialValue?: string | boolean;
  validator?: Validator<T, TValues>;
  elementProp?: string;
  eventType?: 'input' | 'onChange';
})

<input name="quantity" 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.

All parameters are optional:

  • key: model key to bind this input to. If omitted, it will grab the key from the element's .name property.
  • 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: KeySelector;
  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.