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

vanilla-prop-types

v2.0.0

Published

simple prop-types checker

Downloads

35

Readme

vanilla-prop-types

vanilla-prop-types is an approximate implemententation of prop-types that can be used in ES5 environments, NON object-oriented code, and with NO dependencies upon React (the only dependencies are lodash and is-callable). It is intended to be used as an effective props type-checker tool.

this module has four primary module components:

  • PropTypes - syntax carried over from React prop-types
  • checkProps - returns a string validation message if invalid, returns null otherwise
  • validateProps - throws Error if invalid (with the same validation message), does nothing otherwise
  • areValidProps - returns a strict boolean (return true if and only if checkProps version return null)

usage

const {
  PropTypes,
  checkProps,
  validateProps,
  areValidProps,
} = require('vanilla-prop-types');

// create the check function
const allProps = {
  propA: PropTypes.number,
  propB: PropTypes.number.isRequired,
  propC: PropTypes.number.isRequiredOrNull,
  propD: PropTypes.shape({
    shA: PropTypes.array,
    shB: PropTypes.arrayOf(PropTypes.string),
    shC: PropTypes.shape({
      subshA: PropTypes.symbol.isRequired,
    }).isRequired,
  }),
  propE: PropTypes.custom(({prop, propName, props}) => {
    // returning a string from a custom sets the validation error message
    if (prop === 'derp') {
      return 'propE should never be derp!!!!',
    }
  }),
  propF: PropTypes.bool.isRequired.custom(({prop, propName, props}) => {
    // throwing an error also sets the validation error message
    if (!prop) {
      throw new Error('propF should not have be false');
    }
  }),
  propG: PropTypes.number.isRequiredOrNull.custom(({prop, propName, props}) => {
    // otherwise, returning anything TRUTHY is interpreted as a validation fail
    // and will generate a default error message
    // >>> custom validation function fails for propName "propG"
    if (prop === 42) {
      return true;
    }
  }),
};

// return a STRING value or NULL
const check = checkProps(allProps);

// throws error when invalid
const validate = validateProps(allProps);

// use the strict boolean (will NEVER throw)
const isValid = areValidProps(allProps);


// a tyical usage case: validating function input
const myFunc1 = (arg) => {
  const errMessage = check(arg);
  if (errMessage) {
    throw new Error(`invalid props: ${errMessage}`);
  }
  if (isValid(arg)) {
    console.log('the arg is valid!')
  }
  try {
    validate(arg);
  } catch (err) {
    assert(err.message === check(arg))
  }
};

// it can also be effectively used to compose new shapes on the spot
const validateArgs = validateProps({
  apple: PropTypes.string.isRequired,
  banana: PropTypes.string.isRequired,
  isNew: PropTypes
});
const myFunc2 = (apple, banana, {isOld, isNew}) => {
  validate({
    apple,
    propB,
    isNew,
    isOld,
  });
}

Differences from React prop-types

vanilla-prop-types does differ from prop-types in a few significant ways, chiefly

  • checkProps function can take an optional 2nd options arg
  • validateProps function can take an optional 2nd options arg
  • areValidProps function can take an optional 2nd options arg
  • PropTypes.custom has 1 arg {prop, propName, props} (and raw custom functions are not allowed)
  • a custom function check can be added to any normal propType (e.g. PropTypes.number.isRequired.custom(() => 0))
  • the isRequiredOrNull extension is added
  • isExact options for both the outermost object, as well as PropTypes.shape

checkProps validateProps & areValidProps

takes two arguments, and returns either a string message or null

const check = checkProps(propTypes, options);
const validate = validate(propTypes, options);

// lodash is used below for some example code, but is not generally needed
const _ = require('lodash');

options arg

where (optional) options object has the following propTypes:

const optionsTypes = {
  isNullable: PropTypes.bool, // optional - default false
  isNilable: PropTypes.bool,  // optional - default false
  isExact: PropTypes.oneOfType([ // optional - default false
    PropTypes.bool,
    PropTypes.string,
    PropTypes.any.custom(({prop}) => _.isError(prop) || _.isFunction(prop)),
  ]),
  blacklist: PropTypes.arrayOf(PropTypes.string), // default null
  blacklistError: PropTypes.oneOfType([ // default null
    PropTypes.string,
    PropTypes.any.custom(({prop}) => _.isError(prop) || _.isFunction(prop)),
  ]),
  custom: PropTypes.function, // default null
  objectNullError: PropTypes.oneOfType([ // default null
    PropTypes.string,
    PropTypes.any.custom(({prop}) => _.isError(prop) || _.isFunction(prop)),
  ]),
  objectUndefinedError: PropTypes.oneOfType([ // default null
    PropTypes.string,
    PropTypes.any.custom(({prop}) => _.isError(prop) || _.isFunction(prop)),
  ]),
  overrideError: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.any.custom(({prop}) => _.isError(prop) || _.isFunction(prop)),
  ]),
  defaultError: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.any.custom(({prop}) => _.isError(prop) || _.isFunction(prop)),
  ]),
};
  • isNullable the entire props object can be null (but not undefined)
  • isNilable the entire props object can be both null and undefined
  • isExact the entire props object cannot have unaccounted/extra props (if truthy must either be null, a string, an error object, or a function that returns or throws an error or string).
  • blacklist an array of propNames that are never allowed to be populated
  • blacklistError when a custom blacklist error is needed, this option takes a string, error, or error-generating function (that gets checked at runtime) which gets used when the blacklist option is broken
  • custom is a global custom function that is simply not attached to any prop at all (for convenience)
  • objectNullError: when the primary object to be checked is null (and not allowed to be null), this option takes a string, error, or error-generating function (that gets checked at runtime) which gets used instead of default message
  • objectUndefinedError: when the primary object to be checked is undefined (and not allowed to be undefined), this option takes a string, error, or error-generating function (that gets checked at runtime) which gets used instead of default message
  • overrideError: overrides any other error or message encountered. This option takes a string, error, or error-generating function (that gets checked at runtime) which gets used instead of default message
  • defaultError: overrides any check messages that are NOT already errors. This option takes a string, error, or error-generating function (that gets checked at runtime) which gets used instead of default message

Note that blacklist and isExact cannot be used simultaneously (error gets thrown).

If isNullable or isNilable is not used, the default case is that the object being checked must be, at a minimum, an object (or pass the _.isObject(obj) test).

When a custom function is included it will be called with {prop, props, propName}, where prop is simply the full object being checked, props === null, and propName has been hardcoded to be "global custom validation". This keeps the usage of custom functions uniform throughout. In contrast to 'PropTypes.custom' as described below, the prop parameter is always just undefined. Note that all custom validation functions are deemed to PASS if it returns one of [true, null, undefined] and to FAIL if it returns one of [false, <string>, <error>]. All other return values will return a special validation error if any non-allowed return value is encountered.

propTypes arg

The propTypes object works the same as React prop-types (except for custom validation function). Available propTypes include:

The simple ones:

  • any a wildcard placeholder (although isRequired and isRequiredOrNull are enforced)
  • string
  • bool
  • number
  • symbol
  • func
  • object
  • array

The functional ones:

  • oneOf - takes an array of (at least 1) unique values that form an enum (not restricted to strings)
  • instanceOf - takes a single class (e.g. Date)
  • arrayOf - takes a propType (e.g. PropTypes.string)
  • objectOf - takes a propType (e.g. PropTypes.string)
  • oneOfType - takes an array of (at least 1) propTypes (e.g. [PropTypes.any])
  • shape - takes a propTypes object (recursive objects are possible)
  • validator - takes a previously defined validateProps(...) function
const funcPropTypes = {
  derp: PropTypes.oneOf('asdf', 23, {}),
  merp: PropTypes.instanceOf(Date),
  lerp: PropTypes.arrayOf(PropTypes.string),
  nerp: PropTypes.objectOf(PropTypes.string),
  oneOfType: PropTypes.oneOfType([
    PropTypes.string, // as an example
    PropTypes.number,
  ]),
  shape: PropTypes.shape({
    PropTypes.string,
    PropTypes.number,
  }),
  serp: PropTypes.validator(validate),
};

The shape propType allows an option object, which only admits an {isExact: <bool>} value. If {isExact: true} is used, the props being checked is not allowed to have unaccounted/extra props.

The custom type (which is treated special):

  • custom takes a function which will be sent {prop, propName, props} as single argument. Note that custom propType cannot be further chained.

The default behavior is that, for all types, both null and undefined do not trigger a validation error. However, apart from custom, both the simple propTypes and the functional propTypes can be extended with

  • isRequired - neither null nor undefined are allowed
  • isRequiredOrNull - null is allowed, but not undefined
  • custom - same as the standalone, but this allows for easier separation of validation logic

Using an exmple from the simple group

const otherPropTypes = {
  derp: PropTypes.string,
  derpR: PropTypes.string.isRequired,
  derpRN: PropTypes.string.isRequiredOrNull,
  derpC: PropTypes.string.custom(({prop, propName, props}) => {...}),
  derpRC: PropTypes.string.isRequired.custom(({prop, propName, props}) => {...}),
  derpRNC: PropTypes.string.isRequiredOrNull.custom(({prop, propName, props}) => {...}),
};

const complicatedPropTypes = {
  derp: PropTypes.shape({
    merp: PropTypes.oneOf([
      // note that in the following, all of derp is sent in as "props"
      PropTypes.string.isRequiredOrNull.custom(({prop, propName, props}) => {...}),
      PropTypes.number.isRequired,
    ]),
    hasShapeWithOption: PropTypes.shape({arp: PropTypes.any}, {isExact: true}),
  }).isRequiredOrNull.custom(({prop, propName, props}) => {...}),
},

errors vs. error messages in multi-layered objects

In cases with multiple levels (e.g.

  const validate = validateProps({
    top: PropTypes.shape({
      a: PropTypes.string.custom(({prop}) => {
        return 'short message',
      }),
      b: PropTypes.string.custom(({prop}) => {
        return new Error('short message'),
      }),
      c: PropTypes.string.custom(({prop}) => {
        throw new Error('short message'),
      }),
    }),
  });

In the example above, props top.b and top.c will behave exactly the same, and the actual error created in the custom function is the one that will be thrown by the overall validate function. Likewise, when using check instead of validate, the message returned will be simply that of the error (i.e. "short message").

However, for the case top.a above, the message will accumulate an address going upwards through the props chain, finally throwing an error with a such as

PropType validation error at [top, a]: Prop must be a string when included

custom errors

it is possible to inject custom errors at any level, which is the error that will be returned from the validator or checker. The error extension is generally available and overrides ANY error object accumulated so far (unless {isWeak: true} option is used).


const arp4Error = new Error('arp4 error');
const arp5Error = new Error('arp5 error');
const types = {
  arp1: PropTypes.string,
  arp2: PropTypes.string.custom('arp2 error'),
  arp3: PropTypes.string.custom(() => 'arp3 error'),
  arp4: PropTypes.string.custom(arp4Error),
  arp5: PropTypes.string.custom(() => arp5Error),
};

const checkArps = checkProps(types);
const validateArps = validateProps(types);

const mess1 = check({arp1: 23}); // "PropType validation error at [arp1]: Prop must be a string when included"
const mess2 = check({arp2: 23}); // "arp2 error"
const mess3 = check({arp3: 23}); // "arp3 error"
const mess4 = check({arp4: 23}); // "arp4 error"
const mess5 = check({arp5: 23}); // "arp5 error"

try {
  validateArps({arp4: 23});
} catch (err) {
  const isTheSame = (err === arp4Error); // true
}

try {
  validateArps({arp5: 23});
} catch (err) {
  const isTheSame = (err === arp45rror); // true
}

Note that the error extension never does anything unless some previous type checking logic has already blown up. Also note that this error WILL override any previously accrued errors\

Custom error options

When using validateProps as a way to map Error objects to different validation errors, it is convenient to be able to override any error message as long as it hasn't already been explicitly "cast" as an error, e.g.:


const morpTypes = {
  morp1: PropTypes.shape({
    snorp: PropTypes.string,
    knorp: PropTypes.string.error('knorp custom error'),
  }).error('injected custom error if an error not already being propagated up', {isWeak: true}),
  morp2: PropTypes.shape({
    snorp: PropTypes.string,
    knorp: PropTypes.string.error('knorp custom error'),
  }).error('always overrides every error'),
};
const checkMorps = checkProps(morpTypes);

const propsCases = [
  {props: {}, mess: null},
  {props: {morp1: {}, morp2: {}}, mess: null},
  {props: {morp1: {snorp: 'a', knorp: 'b'}, morp2: {snorp: 'c', knorp: 'c'}}, mess: null},
  {props: {morp1: {snorp: 23}}, mess: 'injected custom error if an error not already being propagated up'},
  {props: {morp1: {knorp: 23}}, mess: 'knorp custom error'},
  {props: {morp2: {snorp: 23}}, mess: 'always overrides every error'},
  {props: {morp2: {knorp: 23}}, mess: 'always overrides every error'},
];
_.each(propsCases, propsCase => {
  const mess = checkMorps(propsCase.props);
  assert.strictEqual(mess, propsCase.mess);
});

Some other additions:

Capacity to NOT have to use PropTypes in an object. So it IS valid to write e.g.

const validateCustomString = validateProps(PropTypes.string.isRequired.custom(({prop}) => {
  if (prop.length < 10) {
    throw new Error('Invalid Custom String');
  }
}));

License

Apache-2.0