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

@dramaorg/architecto-eligendi

v4.7.68

Published

[![Downloads][downloads-badge]][downloads] [![Size][size-badge]][size]

Downloads

23

Readme

@dramaorg/architecto-eligendi

Downloads Size

Validation tools for React built on react-waitables

This package provides tools for building efficiently executed validators that depend on bindings and/or waitables. This is useful:

  • for live field-level validation
  • for live form-level validation
  • with validations that depend on dynamically loaded / computed values

Basic Example

The following example demonstrates attaching a validator to a string binding, which is updated by a TextInput component (code for TextInput is included in CodeSandbox).

The validator's state automatically changes as a user interacts with the field. In this case the validator is very simple, checking that the text field isn't empty. Below the input, the page dynamically reflects:

  • what the user entered
  • if the input is valid
  • if the input isn't valid: the associated error

Try it Out – CodeSandbox

import React, { useCallback } from 'react';
import { BindingsConsumer, resolveTypeOrDeferredType, useBinding } from 'react-bindings';
import { checkStringNotEmpty, useValidator } from '@dramaorg/architecto-eligendi';
import { WaitablesConsumer } from 'react-waitables';

import { TextInput } from './TextInput';

export const App = () => {
  const value = useBinding(() => '', { id: 'value', detectChanges: true });
  const valueValidator = useValidator(value, () => checkStringNotEmpty("Shouldn't be empty"), { id: 'valueValidator' });

  const onClear1Click = useCallback(() => value.set(''), [value]);

  return (
    <>
      <div>
        <TextInput value={value} />
        &nbsp;
        <button onClick={onClear1Click}>Clear</button>
      </div>
      <div>
        You entered:&nbsp;
        <BindingsConsumer bindings={value}>{(value) => value}</BindingsConsumer>
      </div>
      <WaitablesConsumer dependencies={valueValidator}>
        {(validator) => (
          <>
            <div>{`Valid: ${String(validator.isValid)}`}</div>
            {!validator.isValid ? <div>{resolveTypeOrDeferredType(validator.validationError)}</div> : null}
          </>
        )}
      </WaitablesConsumer>
    </>
  );
};

Multiple Inputs and Checks Example

In the example above, the validator only depends on a single input value and only performs a single check. However, multiple inputs and multiple operations are supported in a single validator and validators can depend on other validators.

In the following example, we demonstrated stored values and validators (see CodeSandbox for more-complete example with UI) where:

  • either first name or last name (or both) must be entered
  • age must be entered
  • age must be an integer between 0 and 199, inclusive

Try it Out – CodeSandbox

const checkNamePart = (): ValidationChecker<string> => changeStringTrim(checkStringNotEmpty());

const firstName = useBinding(() => '', { id: 'firstName', detectChanges: true });
const firstNameValidator = useValidator(firstName, checkNamePart);

const lastName = useBinding(() => '', { id: 'lastName', detectChanges: true });
const lastNameValidator = useValidator(lastName, checkNamePart);

const nameValidator = useValidator([firstNameValidator, lastNameValidator], (validators) =>
  checkAnyOf(validators, 'First name, last name, or both must be specified')
);

const age = useBinding<number | undefined>(() => undefined, { id: 'age', detectChanges: true });
const ageValidator = useValidator(age, () =>
  preventUndefined(
    [
      checkNumberIsInteger("Fractional values aren't allowed"),
      checkNumberGTE(0, 'Must be >= 0'),
      checkNumberLT(200, 'Must be < 200')
    ],
    'Age is required'
  )
);

const formValidator = useValidators([nameValidator, ageValidator]);

As your use cases become more complex, you'll start to build reusable, composable validators.

Conditional Validation

There are many cases where validation logic needs to be dynamic. With @dramaorg/architecto-eligendi, there are two main ways to introduce dynamism:

  • dynamic validation checkers
  • validator disabling bindings / setDisabledOverride

The validator checker creation function is called every time validation is performed, so the rules you setup can be changed anytime.

Validators can be disabled using one or more of disabledUntil, disabledWhile, and/or disabledWhileUnmodifiedBindings. When a validator is disabled, it it always considered to be valid. All of these options takes one or more bindings, so validators can be disabled/enabled very dynamically.

disabledWhileUnmodifiedBindings helps create more friendly forms by, for example, not providing feedback on inputs that the user hasn't modified yet. Otherwise, in the common case, all fields would initially be in an error state, which isn't necessarily useful. Consider using disabledWhileUnmodifiedBindings on all or most validators directly associated with inputs (see Final Validation section below as well).

If you need to override the automatically-calculated behavior, validators expose setDisabledOverride, which can be used to forcibly enable or disable a validator or to clear the override.

Example

In the following example, we use disabledWhileUnmodifiedBindings so that firstNameValidator is disabled until firstName is modified. firstName is usually modified by calling set.

const firstName = useBinding(() => '', { id: 'firstName', detectChanges: true });
const firstNameValidator = useValidator(firstName, makeRequiredStringChecker, { disabledWhileUnmodified: firstName });

Final Validation

In addition to interactive validation, we often need "final" validation before, for example, submitting data to a server.

During interactive validation, we often have disabledWhileUnmodifiedBindings associated with inputs. However, if a user tries to submit a form with incomplete data, where they've accidentally skipped a field, for example, we then want to make sure we give clear feedback at that point. The finalizeValidation utility is used for these cases and to generally wait for validation to finish.

You may also want to use useValidators which is a shorthand for combining multiple validators together.

const formValidator = useValidators([firstNameValidator, lastNameValidator]);

const onDoneClick = () =>
  finalizeValidation(formValidator, {
    fieldBindings: { firstName, lastName },
    onValid: ({ firstName, lastName }) => { … }
  });

finalizeValidation generally:

  • marks the specified bindings as modified if they're not already
  • for bindings newly marked as modified, calls triggerChangeListeners, which in turn resets the associated validators
  • locks the specified bindings
  • waits for the validator to finish
  • extracts the specified bindings' values
  • unlocks the specified bindings
  • calls either onValid or onInvalid

It returns a function that can be used to cancel validation, if desired, and also a promise for the result.

Extension

This package provides basic functionality for string and number validation and, more importantly, provides tools for building your own performant validators.

For extending these capabilities, be sure to checkout our API Docs to get a more-complete picture of the building blocks.

Examples of extensions others might add support for are:

  • BigNumber
  • DateTime (Luxon)
  • Formal schemas and/or other validation tools (ex. Yaschema, Joi)

Naming

The API using the following prefixes for naming conventions:

  • check - a validation checking step
  • change - a value transformation step
  • allow - allow a set of values and pass the value through, for further checking, without the allowed types, since they were already checked
  • prevent - disallow a set of values and pass the value through if not disallowed, for further checking, without the disallowed types, since they were already checked
  • select - continue validation with a different value

Naming Convention Examples

checkStringNotEmpty()
changeStringTrim(checkStringNotEmpty())
allowNull(checkStringNotEmpty())
preventNull(checkStringNotEmpty())
selectValue(Math.random(), checkNumberGT(0.5))

API Docs

Thanks

Thanks for checking it out. Feel free to create issues or otherwise provide feedback.

Be sure to check out our other TypeScript OSS projects as well.