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

redux-revalidate

v0.1.0

Published

Validate your Redux store state with revalidate

Downloads

7

Readme

redux-revalidate

Travis branch npm

Validate your Redux store state with revalidate.

The typical use case for revalidate is creating validation function Redux Form components. However, if you're not using Redux Form but still want to validate your Redux store, then you can use redux-revalidate to automatically perform the validations. Redux-revalidate validates every new state produced by your reducer function(s) according to the validate function you create with revalidate itself. Redux-revalidate manages the error messages by adding its own state to the store.

Install

$ npm install --save redux-revalidate

Usage

Redux-revalidate exports a few functions to allow seamless integration with revalidate.

validateStore

The simplest approach is to validate your entire store thanks to the nested field support of revalidate. You can use validateStore as a store enhancer to validate your entire store according to the provided validate function.

validateStore takes as its first argument your validate function and an optional options object as the second argument. validateStore will add an object to the root of your store that will contain any possible error messages for other properties in your store. The default key for this object is errors. You can provide a different key if you're already using errors as a key yourself. Just provide the errorKey option in the optional options argument: e.g.validateStore(validate, { errorKey: 'myErrors' }).

The API for validateStore is still new, but for now it will immediately validate your store when Redux runs your reducer function the first time. This means error messages may immediately appear in your store before you've dispatched any actions. If you want something more robust that handles whether a particular field value is touched and when it should be validated, then you should consider using Redux Form with revalidate.

Additionally, validateStore will validate your store after any dispatched action. Basically, if Redux calls your reducer function, then redux-revalidate will validate the new state right after.

// ES2015 imports
import { createStore } from 'redux';
import { validateStore } from 'redux-revalidate';

import {
  combineValidators,
  composeValidators,
  isAlphabetic,
  isNumeric,
  isRequired,
} from 'revalidate';

// CJS imports
const createStore = require('redux').createStore;
const validateStore = require('redux-revalidate').validateStore;
const r = require('revalidate');
const combineValidators = r.combineValidators;
const composeValidators = r.composeValidators;
const isAlphabetic = r.isAlphabetic;
const isNumeric = r.isNumeric;
const isRequired = r.isRequired;

// Usage
const UPDATE_DOG_NAME = 'UPDATE_NAME';
const UPDATE_DOG_AGE = 'UPDATE_AGE';

const updateDogName = (payload) => ({ payload, type: UPDATE_DOG_NAME });
const updateDogAge = (payload) => ({ payload, type: UPDATE_DOG_AGE });

const INITIAL_STATE = {
  name: '',
  age: '',
};

function reducer(state = INITIAL_STATE, action) {
  switch (action.type) {
    case UPDATE_DOG_NAME:
      return { ...state, name: action.payload };

    case UPDATE_DOG_AGE:
      return { ...state, age: action.payload };

    default:
      return state;
  }
}

const validate = combineValidators({
  name: isRequired('Dog Name'),

  age: composeValidators(
    isRequired,
    isNumeric
  )('Dog Age'),
});

const store = createStore(reducer, validateStore(validate));

console.log(store.getState());

// { name: '',
//   age: '',
//   errors: { name: 'Dog Name is required',
//             age: 'Dog Age is required' } }

store.dispatch(updateDogName('Tucker'));
console.log(store.getState());

// { name: 'Tucker',
//   age: '',
//   errors: { age: 'Dog Age is required' } }

store.dispatch(updateDogAge('abc'));
console.log(store.getState());

// { name: 'Tucker',
//   age: 'abc',
//   errors: { age: 'Dog Age must be numeric' } }

store.dispatch(updateDogAge('10'));
console.log(store.getState());

// { name: 'Tucker', age: '10', errors: {} }

With optional errorKey option:

const store = createStore(
  reducer,
  validateStore(validate, { errorKey: 'myErrors' })
);

console.log(store.getState());

// { name: '',
//   age: '',
//   myErrors: { name: 'Dog Name is required',
//               age: 'Dog Age is required' } }

With other enhancers like middleware

import { applyMiddleware, compose, createStore } from 'redux';
import thunkMiddlware from 'redux-thunk';

// ...

function delayUpdateDogName(payload, time) {
  return (dispatch) => new Promise(resolve => {
    setTimeout(() => {
      dispatch(updateDogName(payload));
      resolve();
    }, time);
  });
}

// ...

const store = createStore(
  reducer,
  compose(
    validateStore(validate),
    applyMiddleware(thunkMiddlware)
  )
);

console.log(store.getState());

// { name: '',
//   age: '',
//   errors: { name: 'Dog Name is required',
//             age: 'Dog Age is required' } }

store.dispatch(delayUpdateDogName('Tucker', 1000))
  .then(() => {
    console.log(store.getState());

    // { name: 'Tucker',
    //   age: '',
    //   errors: { age: 'Dog Age is required' } }
  });

With nested properties

As previously mentioned, arbitrarily nested properties in the store can be validated too:

import { combineReducers, createStore } from 'redux';

import {
  combineValidators,
  composeValidators,
  isAlphabetic,
  isNumeric,
  isRequired,
} from 'revalidate';

import { validateStore } from 'redux-revalidate';

const UPDATE_CONTACT_NAME = 'UPDATE_CONTACT_NAME';
const UPDATE_CONTACT_AGE = 'UPDATE_CONTACT_AGE';
const UPDATE_DOG_BREED = 'UPDATE_DOG_BREED';
const UPDATE_DOG_NAME = 'UPDATE_DOG_NAME';
const UPDATE_DOG_AGE = 'UPDATE_DOG_AGE';

const updateContactName = (payload) => ({ payload, type: UPDATE_CONTACT_NAME });
const updateContactAge = (payload) => ({ payload, type: UPDATE_CONTACT_AGE });
const updateDogBreed = (payload) => ({ payload, type: UPDATE_DOG_BREED });
const updateDogName = (payload) => ({ payload, type: UPDATE_DOG_NAME });
const updateDogAge = (payload) => ({ payload, type: UPDATE_DOG_AGE });

const INITIAL_CONTACT = {
  name: '',
  age: '',
};

function contactReducer(state = INITIAL_CONTACT, action) {
  switch (action.type) {
    case UPDATE_CONTACT_NAME:
      return { ...state, name: action.payload };

    case UPDATE_CONTACT_AGE:
      return { ...state, age: action.payload };

    default:
      return state;
  }
}

const INITIAL_DOG = {
  breed: '',
  name: '',
  age: '',
};

function dogReducer(state = INITIAL_DOG, action) {
  switch (action.type) {
    case UPDATE_DOG_BREED:
      return { ...state, breed: action.payload };

    case UPDATE_DOG_NAME:
      return { ...state, name: action.payload };

    case UPDATE_DOG_AGE:
      return { ...state, age: action.payload };

    default:
      return state;
  }
}

const validate = combineValidators({
  'contact.name': isRequired('Name'),
  'contact.age': composeValidators(
    isRequired,
    isNumeric
  )('Age'),

  'dog.breed': isAlphabetic('Breed'),
  'dog.name': isRequired('Name'),
  'dog.age': composeValidators(
    isRequired,
    isNumeric
  )('Age'),
});

const reducer = combineReducers({
  contact: contactReducer,
  dog: dogReducer,
});

const store = createStore(
  reducer,
  validateStore(validate)
);

store.subscribe(() => console.log(store.getState()));

console.log(store.getState());

// { contact: { name: '', age: '' },
//   dog: { breed: '', name: '', age: '' },
//   errors:
//    { contact: { name: 'Name is required', age: 'Age is required' },
//      dog: { name: 'Name is required', age: 'Age is required' } } }

store.dispatch(updateContactName('Joe'));

// { contact: { name: 'Joe', age: '' },
//   dog: { breed: '', name: '', age: '' },
//   errors:
//    { contact: { age: 'Age is required' },
//      dog: { name: 'Name is required', age: 'Age is required' } } }

store.dispatch(updateContactAge('abc'));

// { contact: { name: '', age: 'abc' },
//   dog: { breed: '', name: '', age: '' },
//   errors:
//    { contact: { name: 'Name is required', age: 'Age must be numeric' },
//      dog: { name: 'Name is required', age: 'Age is required' } } }

store.dispatch(updateContactAge('30'));

// { contact: { name: 'Joe', age: '30' },
//   dog: { breed: '', name: '', age: '' },
//   errors:
//    { contact: {},
//      dog: { name: 'Name is required', age: 'Age is required' } } }

store.dispatch(updateDogBreed('123'));

// { contact: { name: 'Joe', age: '30' },
//   dog: { breed: '123', name: '', age: '' },
//   errors:
//    { contact: {},
//      dog:
//       { breed: 'Breed must be alphabetic',
//         name: 'Name is required',
//         age: 'Age is required' } } }

store.dispatch(updateDogBreed('Sheltie'));

// { contact: { name: 'Joe', age: '30' },
//   dog: { breed: 'Sheltie', name: '', age: '' },
//   errors:
//    { contact: {},
//      dog: { name: 'Name is required', age: 'Age is required' } } }

store.dispatch(updateDogName('Tucker'));

// { contact: { name: 'Joe', age: '30' },
//   dog: { breed: 'Sheltie', name: 'Tucker', age: '' },
//   errors: { contact: {}, dog: { age: 'Age is required' } } }

store.dispatch(updateDogAge('abc'));

// { contact: { name: 'Joe', age: '30' },
//   dog: { breed: 'Sheltie', name: 'Tucker', age: 'abc' },
//   errors: { contact: {}, dog: { age: 'Age must be numeric' } } }

store.dispatch(updateDogAge('10'));

// { contact: { name: 'Joe', age: '30' },
//   dog: { breed: 'Sheltie', name: 'Tucker', age: '10' },
//   errors: { contact: {}, dog: {} } }

Caveat:

If you use combineReducers like the previous example, you might see a warning like this:

Unexpected key "errors" found in previous state received by the reducer.
Expected to find one of the known reducer keys instead: "contact", "dog".
Unexpected keys will be ignored.

To fix this issue, import the function errorsReducer and mount it at the errors key or whatever custom key you use to hold your error message state:

import { errorsReducer } from 'redux-revalidate';

// ...

// With default key
const reducer = combineReducers({
  contact: contactReducer,
  dog: dogReducer,
  errors: errorsReducer,
});

// With custom `errorKey` supplied to `validateStore`
const reducer = combineReducers({
  contact: contactReducer,
  dog: dogReducer,
  myErrors: errorsReducer,
});

validateReducer

If you want finer-grain control over what portions of your store are validated and where to store the error message state, you can opt to wrap your reducer function(s) with the validateReducer function.

validateReducer is a curried function. The first invocation takes the validate function as the first argument and optional options argument as the second argument. The returned function takes a reducer function as the first argument and preloaded/initial state as the second argument. In fact, validateStore uses validateReducer internally to just wrap your root reducer. Here is one of the previous examples with the contact and dog but using validateReducer to validate the dog state and store any dog validation errors in the same state as the dog:

import { combineReducers, createStore } from 'redux';

import {
  combineValidators,
  composeValidators,
  isAlphabetic,
  isNumeric,
  isRequired,
} from 'revalidate';

import {
  errorsReducer,
  validateReducer,
  validateStore,
} from 'redux-revalidate';

// ...

const validate = combineValidators({
  'contact.name': isRequired('Name'),

  'contact.age': composeValidators(
    isRequired,
    isNumeric
  )('Age'),
});

const validateDog = combineValidators({
  breed: isAlphabetic('Breed'),
  name: isRequired('Name'),
  age: composeValidators(
    isRequired,
    isNumeric
  )('Age'),
});

const reducer = combineReducers({
  contact: contactReducer,

  dog: validateReducer(
    validateDog, { errorKey: 'dogErrors' }
  )(dogReducer),

  errors: errorsReducer,
});

const store = createStore(
  reducer,
  validateStore(validate)
);

store.subscribe(() => console.log(store.getState()));

console.log(store.getState());

// { contact: { name: '', age: '' },
//   dog:
//    { breed: '',
//      name: '',
//      age: '',
//      dogErrors: { name: 'Name is required', age: 'Age is required' } },
//   errors: { contact: { name: 'Name is required', age: 'Age is required' } } }

store.dispatch(updateContactName(''));

// { contact: { name: '', age: '' },
//   dog:
//    { breed: '',
//      name: '',
//      age: '',
//      dogErrors: { name: 'Name is required', age: 'Age is required' } },
//   errors: { contact: { name: 'Name is required', age: 'Age is required' } } }

store.dispatch(updateContactName('Joe'));

// { contact: { name: 'Joe', age: '' },
//   dog:
//    { breed: '',
//      name: '',
//      age: '',
//      dogErrors: { name: 'Name is required', age: 'Age is required' } },
//   errors: { contact: { age: 'Age is required' } } }

store.dispatch(updateContactAge('abc'));

// { contact: { name: 'Joe', age: 'abc' },
//   dog:
//    { breed: '',
//      name: '',
//      age: '',
//      dogErrors: { name: 'Name is required', age: 'Age is required' } },
//   errors: { contact: { age: 'Age must be numeric' } } }

store.dispatch(updateContactAge('30'));

// { contact: { name: 'Joe', age: '30' },
//   dog:
//    { breed: '',
//      name: '',
//      age: '',
//      dogErrors: { name: 'Name is required', age: 'Age is required' } },
//   errors: { contact: {} } }

store.dispatch(updateDogBreed('123'));

// { contact: { name: 'Joe', age: '30' },
//   dog:
//    { breed: '123',
//      name: '',
//      age: '',
//      dogErrors:
//       { breed: 'Breed must be alphabetic',
//         name: 'Name is required',
//         age: 'Age is required' } },
//   errors: { contact: {} } }

store.dispatch(updateDogBreed('Sheltie'));

// { contact: { name: 'Joe', age: '30' },
//   dog:
//    { breed: 'Sheltie',
//      name: '',
//      age: '',
//      dogErrors: { name: 'Name is required', age: 'Age is required' } },
//   errors: { contact: {} } }

store.dispatch(updateDogName('Tucker'));

// { contact: { name: 'Joe', age: '30' },
//   dog:
//    { breed: 'Sheltie',
//      name: 'Tucker',
//      age: '',
//      dogErrors: { age: 'Age is required' } },
//   errors: { contact: {} } }

store.dispatch(updateDogAge('abc'));

// { contact: { name: 'Joe', age: '30' },
//   dog:
//    { breed: 'Sheltie',
//      name: 'Tucker',
//      age: 'abc',
//      dogErrors: { age: 'Age must be numeric' } },
//   errors: { contact: {} } }

store.dispatch(updateDogAge('10'));

// { contact: { name: 'Joe', age: '30' },
//   dog:
//    { breed: 'Sheltie',
//      name: 'Tucker',
//      age: '10',
//      dogErrors: {} },
//   errors: { contact: {} } }