Giter Club home page Giter Club logo

revalidate's Introduction

revalidate

npm Travis branch Codecov

Elegant and composable validations.

Revalidate is a library for creating and composing together small validation functions to create complex, robust validations. There is no need for awkward configuration rules to define validations. Just use functions.

All right. No more upselling. Just look at an example ❤️.

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

// Or ES5
var r = require('revalidate');
var createValidator = r.createValidator;
var composeValidators = r.composeValidators;
var combineValidators = r.combineValidators;
var isRequired = r.isRequired;
var isAlphabetic = r.isAlphabetic;
var isNumeric = r.isNumeric;

// Usage
const dogValidator = combineValidators({
  name: composeValidators(
    isRequired,
    isAlphabetic
  )('Name'),

  age: isNumeric('Age')
});

dogValidator({}); // { name: 'Name is required' }

dogValidator({ name: '123', age: 'abc' });
// { name: 'Name must be alphabetic', age: 'Age must be numeric' }

dogValidator({ name: 'Tucker', age: '10' }); // {}

Install

Install with yarn or npm.

yarn add revalidate
npm install --save revalidate

Getting Started

Revalidate has a host of options along with helper functions for building validations and some common validation functions right out of the box. To learn more, check out the docs at revalidate.jeremyfairbank.com.

Redux Form

Just one more example! You might have heard about revalidate through Redux Form. Revalidate was originally conceived as a library for writing validation functions for Redux Form. Revalidate is still a great companion to Redux Form! Here is the simple synchronous form validation from Redux Form's docs rewritten to use revalidate:

import React from 'react'
import { Field, reduxForm } from 'redux-form'

import {
  createValidator,
  composeValidators,
  combineValidators,
  isRequired,
  hasLengthLessThan,
  isNumeric
} from 'revalidate'

const isValidEmail = createValidator(
  message => value => {
    if (value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
      return message
    }
  },
  'Invalid email address'
)

const isGreaterThan = (n) => createValidator(
  message => value => {
    if (value && Number(value) <= n) {
      return message
    }
  },
  field => `${field} must be greater than ${n}`
)

const customIsRequired = isRequired({ message: 'Required' })

const validate = combineValidators({
  username: composeValidators(
    customIsRequired,

    hasLengthLessThan(16)({
      message: 'Must be 15 characters or less'
    })
  )(),

  email: composeValidators(
    customIsRequired,
    isValidEmail
  )(),

  age: composeValidators(
    customIsRequired,

    isNumeric({
      message: 'Must be a number'
    }),

    isGreaterThan(17)({
      message: 'Sorry, you must be at least 18 years old'
    })
  )()
})

const warn = values => {
  const warnings = {}
  if (values.age < 19) {
    warnings.age = 'Hmm, you seem a bit young...'
  }
  return warnings
}

const renderField = ({ input, label, type, meta: { touched, error, warning } }) => (
  <div>
    <label>{label}</label>
    <div>
      <input {...input} placeholder={label} type={type}/>
      {touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))}
    </div>
  </div>
)

const SyncValidationForm = (props) => {
  const { handleSubmit, pristine, reset, submitting } = props
  return (
    <form onSubmit={handleSubmit}>
      <Field name="username" type="text" component={renderField} label="Username"/>
      <Field name="email" type="email" component={renderField} label="Email"/>
      <Field name="age" type="number" component={renderField} label="Age"/>
      <div>
        <button type="submit" disabled={submitting}>Submit</button>
        <button type="button" disabled={pristine || submitting} onClick={reset}>
          Clear Values
        </button>
      </div>
    </form>
  )
}

export default reduxForm({
  form: 'syncValidation',  // a unique identifier for this form
  validate,                // <--- validation function given to redux-form
  warn                     // <--- warning function given to redux-form
})(SyncValidationForm)

revalidate's People

Contributors

boxfoot avatar brolin avatar greenkeeperio-bot avatar jfairbank avatar jssel avatar zagitta avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

revalidate's Issues

How to validate a checkbox has been selected

I'm really struggling to figure out how to create a validator that ensure the checkbox for "I agree to the terms and conditions" has been checked.

import { FORM_ERROR } from 'final-form';
import React, { useContext } from 'react';
import { Field, Form as FinalForm } from 'react-final-form';
import { combineValidators, createValidator, composeValidators, isRequired } from 'revalidate';
import { Button, Form, Grid, Header, Label, Checkbox } from 'semantic-ui-react';
import ErrorMessage from '../../app/common/form/ErrorMessage';
import TextInput from '../../app/common/form/TextInput';
import { IUserFormValues } from '../../app/models/user';
import { RootStoreContext } from '../../app/stores/rootStore';

interface IProps {
initialValues: IUserFormValues;
}

const isChecked = createValidator(
message => value => {
if (value && Boolean(value) === false) {
return message
}
},
'Required'
);

const customIsRequired = isRequired({ message: 'Required' });

const isValidEmail = createValidator(
message => value => {
if (value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$/i.test(value)) {
return message
}
},
'Invalid email address'
);

const validate = combineValidators({
firstName: customIsRequired,
lastName: customIsRequired,
email: composeValidators(
customIsRequired,
isValidEmail
)(),
password: customIsRequired,
chkAgree: composeValidators(
customIsRequired,
isChecked)()
});

const Register: React.FC = ({ initialValues }) => {

const rootStore = useContext(RootStoreContext);
const { register } = rootStore.userStore;

return (
    <Grid textAlign='center' verticalAlign='middle'>
        <Grid.Column style={{ maxWidth: 450 }}>
            <Header as='h2' textAlign='center' >
                Sign up
            </Header>

            <FinalForm
                initialValues={initialValues}
                onSubmit={(values: IUserFormValues) => register(values).catch(error => ({
                    [FORM_ERROR]: error
                }))}
                validate={validate}
                render={({
                    handleSubmit,
                    submitting,
                    submitError,
                    invalid,
                    pristine,
                    dirtySinceLastSubmit
                }) => (
                        <Form onSubmit={handleSubmit} error>
                        <input type='hidden' name='userType' />
                            <p>
                                We just need a few basic details to get you started on your journey to becoming a hotshot!
                            </p>

                            <Field
                                name='firstName'
                                component={TextInput}
                                placeholder='First name'
                            />
                            <Field
                                name='lastName'
                                component={TextInput}
                                placeholder='Last name'
                            />
                            <Field
                                name='preferredName'
                                component={TextInput}
                                placeholder='Preferred name'
                            />
                            <Field
                                name='email'
                                component={TextInput}
                                placeholder='Email'
                            />
                            <Field
                                name='password'
                                component={TextInput}
                                placeholder='Password'
                                type='password'
                            />                        

                            <Form.Field>
                                <input type='checkbox' name='chkAgree'/>
                                <label>I agree</label>
                            </Form.Field>


                            {submitError && !dirtySinceLastSubmit && (
                                <ErrorMessage error={submitError} />
                            )}
                            <Button
                                style={{ textAlign: "center" }}
                                disabled={(invalid && !dirtySinceLastSubmit) || pristine}
                                loading={submitting}
                                positive
                                content='Register'
                            />
                        </Form>
                    )}
            />
        </Grid.Column>
    </Grid>
)

}

export default Register;

//

/*

I agree to the Terms and conditions and Privacy policy

*/

Accessing other fields in array structure

Hi
I'm trying to create a validator on an array of passenger (object structure). In each passenger, I have his firstName and lastName.

I have to trigger firstName and lastName error when we have the same passenger name (first name + last name).

I'd like to know if we can easily access the firstName in the lastName composeValidator and also in the other way back (accessing to the field lastName in the firstName composeValidator)

Is it a great way to do so ?

"passengers[]": {
	name: createValidator(
		() => passengerName => {
			return {
				lastName: lastNameValidator(passengerName.firstName),
				firstName: firstNameValidator(passengerName.lastName),
				gender: isRequired({
					message: validationMessages.genderRequired,
				}),
			};
		},
		message
	)(message),

Test helpers

It might be nice to export some helpers to test validation results (whether there was an error or not in the result).

How pass dynamic field name to composeValidators

Can I pass a dynamic field name to composeValidators so the returned message includes field name as I reuse it for different fields with same validation?

I want to do something like this:

const textValidator = composeValidators(
  isRequired,
  isAlphabetic
)(dynamicFieldName);

var fieldName1 = 'First Name'
var fieldName2 = 'Last Name'
textValidator()(fieldName1);      // 'First Name is required'
textValidator()(fieldName2);      // 'Last Name is required'

Typescript definition file

Hey, I really like revalidate, it is very helpful but in my project I am using Typescript so it would be nice to have the appropriated definitions.

If nobody is working on it, I can maybe do something.

Still Maintained ?

Hello

We are heavily relying on revalidate today.

We are wondering if the lib is still maintained ?

Bests

Ability to translate error messages

Hi! Thanks for this great library, validation becomes really enjoyable.

Is it possible to change default error messages (to support different languages for example) without copy-paste all validator or passing custom message on every use?

New common validator: isRequiredIf

It might be useful to have a validator that requires a value based on other values. Something like

const validate = combineValidators({
  foo: isRequiredIf(values => values.bar)('Foo'),
});

validate({});             // undefined
validate({ bar: 'baz' }); // { foo: 'Foo is required' }

return `null` when no errors

Why not return null when there are no validation errors?

That way we could do something like:

// Usage
const dogValidator = combineValidators({
  name: composeValidators(
    isRequired,
    isAlphabetic
  )('Name'),

  age: isNumeric('Age')
});

const dog = { name: 'Tucker', age: '10' };

// style 1
const errors = dogValidator(dog);
if (errors) {
  throw new ValidationError(errors);
}

db.save(dog);

// style 2
const errors = dogValidator(dog);
if (!errors)
  db.save(dog);
}

I'm aware of the assertion helpers, but in my opinion they shouldn't be really necessary.

Component Props within validator

Hello Jeremy,
Thanks for creating revalidate. I really love how the validators can be combined and reused. Keep up the good work.
This is more of a question than an issue. Redux-form's validate function is as follows:
validate : (values:Object, props:Object) => errors:Object [optional]
My question is about the second argument props which can be utilized if the component's props are needed within validation. How can the component props be passed to createValidator/composeValidator/combineValidators, so that it can be used in validation? Is this even possible?

Thanks once again.
Vinay.

qn: usage

Hi,

I couldn't make it work as in the Readme.

const propValidator = combineValidators({
  schema: composeValidators(
    isRequired,
    // isAlphabetic
  )({ multiple: true }),
  name: isRequired
});

propValidator({ schema, name })

any help appreciated.
thanks.

stack

Please provide a string or configuration object with a `field` or `message` property
     
     at getMessage (node_modules/revalidate/lib/createValidator.js:35:9)
     at validator (node_modules/revalidate/lib/createValidator.js:45:19)
     at validateWithValidator (node_modules/revalidate/lib/internal/createValidatorWithMultipleErrors.js:17:10)
     at node_modules/revalidate/lib/internal/createValidatorWithMultipleErrors.js:38:26
     at Array.reduce (native)
     at composedValidator (node_modules/revalidate/lib/internal/createValidatorWithMultipleErrors.js:37:48)
     at node_modules/revalidate/lib/internal/internalCombineValidators.js:40:12
     at Array.reduce (native)
     at valuesValidator (node_modules/revalidate/lib/internal/internalCombineValidators.js:32:36)
     at validate (app/utils/validate.js:10:13)

How to get field value in message creator function?

Thank you for this library.
I'm trying to create a custom validator using createValidator helper
In the message I need use current value, how to achieve this?

It looks something like this

const myValidator = createValidator(
  message => value => message,
  (field, value) => `${field} has value ${value}`
);

Possible Error in documentation for pull-request #46

Hi

Regarding this pull request #46

I expect the following code to work :

export const passwordFieldValidator = composeValidators(
	isRequired({
		id: 'error.password.required',
		description: 'Error message password required',
		defaultMessage: 'You must provide a password'
	}),
	hasLengthGreaterThan(5)(
		id: 'error.password.required',
		description: 'Error message password required',
		defaultMessage: 'You must provide a password'
	})
)('password');

But It doesn't and I got the Error message :

Error: Please provide a string or configuration object with a `field` or `message` property

However, in order to work, I have to put a field property like this :

export const passwordFieldValidator = composeValidators(
	isRequired({
		field: {
			id: 'error.password.required',
			description: 'Error message password required',
			defaultMessage: 'You must provide a password'
		}
	}),
	hasLengthGreaterThan(5)({
		field: {
			id: 'error.password.required',
			description: 'Error message password required',
			defaultMessage: 'You must provide a password'
		}
	})
)('password');

If this is the case, it seems that the example in the documentation is not correct here http://revalidate.jeremyfairbank.com/usage/createValidator.html.

In the current documentation the example code is :

const requiredName = isRequired({
  id: 'name',
  defaultMessage: 'Name is required',
});

whereas it should be :

const requiredName = isRequired({
   field : {
       id: 'name',
      defaultMessage: 'Name is required',
   }
});

Am I correct or did I miss something ?

Best regards

Mark others fields as erroneous

Hi

Given the following code :

capture d ecran 2018-12-06 a 14 30 50

capture d ecran 2018-12-06 a 14 33 17

What I want to do is to mark all the credit card fields as erroneous if the creditCardValidator fail to validate.

The actuel behaviour is that only the number field is erroneous.

Also if the creditCardValidator succeed to validate, all the credit card field must be valid.

How can I do that please ?

Best regards

isRequiredIf doesn't work well with array items

The newly added isRequiredIf checks all values of a form to see if a field should be required. But in my case, I only want to check if a specific field within the same object as another field is populated.

e.g.:

{
  childFields: [
    {
      shouldFooBeRequired: true,
      foo: '',
    },
    {
      shouldFooBeRequired: false,
      foo: '',
    },
  ],
}

There's no way with isRequiredIf to make childFields[0].foo be required, while childFields[1].foo stays optional.

I was thinking of submitting a PR to pass value as a second parameter into the condition function, but that's no good as I don't think it will enable me to figure out where value lives within the array.

Any suggestions? Thanks!

Named errors

It might be nice to have the returned errors be named, especially when returning multiple errors. In this case it would return an object instead of an array.

Async validation

It would be nice to incorporate async validations. Definitely need time to think through a frictionless API that works nicely with the current API for sync validations.

Test composeValidator when others values are needed

Hi

I have the following validator which check if the passenger count do not exceed 9. This validator needs also the value of the fields infants and children :

export const maxPassengersNumber = (infants, children) => {
	return createValidator(
		message => (value, allValues) => {
			const passengersCount = +value + +allValues[infants] + +allValues[children];
			if (passengersCount > 9) {
				return message;
			}
		},
		validationMessages.maxPassengersNumbers.defaultMessage
	);
};

This validator is also used in a composeValidators as below :

export const adultPassengerNumberValidator = composeValidators(
	mustNotEqualZero({
		message: validationMessages.adultsPassengers
	}),
	maxPassengersNumber("infants", "children")({
		message: validationMessages.maxPassengersNumbers
	}),
)('adult');

My question is how to test adultPassengerNumberValidator ?

Here is my first attempt :

describe('adultPassengerNumberValidator', () => {
	it("should not be equal to zero", () => {
		expect(hasError(adultPassengerNumberValidator(0))).to.be.true; // <--- this test is ok
	});
	it("should not be exceed 9 passengers", () => {
		expect(hasError(adultPassengerNumberValidator(/*???*/))).to.be.true; // <--- how ?
	});
});

Thanks !!

Best regards

Add docs

The README is starting to feel bloated. It would be nice to create some real docs to clean up the README.

Support arbitrary form value sources

#10 brought up a good point that it would be nice to automatically support Immutable.js. Instead of tying revalidate's implementation to Immutable.js, I would like to see support for arbitrary data sources. More than likely this would require a new options object that could be passed into combineValidators with a serializeValues option.

const options = {
  serializeValues: values => values.toJS(),
};

const myValidator = combineValidators({
  foo: isRequired('Foo'),
  bar: isAlphaNumeric('Bar'),
}, options);

Because Immutable.js is so popular it might make sense to expose a version of combineValidators that takes care of supplying this option for Immutable.js. Something like this:

import { combineValidators as defaultCombineValidators } from 'revalidate';

const options = {
  serializeValues: values => values.toJS(),
};

export function combineValidators(validators) {
  return defaultCombineValidators(validators, options);
}

isNumeric – misleading name?

The built-in isNumeric validator has unexpected behavior, I think. I'd expect a validator named isNumeric to support any real number, i.e.; 5, 100.23 and maybe even "4,54", but at least the first two.

Given its current behavior, do you think the name isInteger is more suitable, because only integers pass?

Nested composed validators do not curry as expected.

Nested composed validators do not curry as expected.

* This may just be a misunderstanding of the lib is intended to work.

Expected:

// imagine I have created a validator `containsNoNumbers`
const isName = composeValidators(hasLengthBetween(2, 32), containsNoNumbers)
composeValidators(isRequired, isName)('First Name')
composeValidators(isRequired, isName)('Last Name')

isRerquired works as expected and allows the field to propagate through. isName however always returns a function. I can fix the issue by doing something like:

const isName = composeValidators(hasLengthBetween(2, 32), containsNoNumbers)
composeValidators(isRequired, isName('First Name'))('First Name')
composeValidators(isRequired, isName('Last Name'))('Last Name')

but it seems as though the composed validator should work exactly the same as a created validator.

The composed validator is also curried and takes the same arguments as an individual validator made with createValidator.

compose validators with composed Validators

Hi

I'm wondering if it is possible to compose validators with composed validators ?

export const firstnameValidator = composeValidators(
    hasLengthGreaterThan(1)({
        message: ""
    }),
    hasLengthLessThan(31)({
        message: ""
    })
)('firstname');

export const requiredFirstnameValidator = composeValidators(
    isRequired({
        message: ""
    }),
    firstnameValidator
)('firstname');

Nested fields

The structure of my form is split into two objects and the keys are 'contact.title', 'contact.firstName' etc... From what I can tell CombineReducers is expecting a flat structure. Is this something you could add support for?

Thanks!

Passing "contextual" data into validation method

👋 @jfairbank !

I often need to validate a field based on some information outside of any of the fields being validated. That's what I mean by "contextual" data in this issue's title. For instance, in a web app that manages books, you may want the books to have a unique name. So to validate the name of one book, the validation method needs all books to know if there is a duplicate.

To do this, would you recommend putting this "extra" data in the object to validated, and then not adding any validation on that key within combineValidators?

To give an example, using the dog example from the docs:

const dogValidator = combineValidators({
  name: composeValidators(
    isAlphabetic,
    createValidator(
      message => (value, allValues) => {
        if (allValues.dogNames.indexOf(value) !== -1) {
          return message;
        }
      },

      field => `${field} must be unique`
    )
  )('Name'),

  age: isNumeric('Age')
});

const result = dogValidator({ name: 'larry', age: 'abc', dogNames: ['larry', 'kathryn'] });

Do you think this is fine, or do you have another suggestion on how best to do this? Thanks!


At first, I tried to do...

 createValidator(
  message => (value, allValues, dogNames) => {
    if (dogNames.indexOf(value) !== -1) {
      return message;
    }
  },

  field => `${field} must be unique`
)

const result = dogValidator({ name: 'larry', age: 'abc'}, ['larry', 'kathryn']);

but that didn't work :)

Combining array-level and nested validators

I'm trying to combine a validator which should be applied to elements within an array, and also a validator which applies to a field within each item in that array, i.e.:

const validator = combineValidators({
  "people[]": customNameAndAgeUniqueValidator({ message: "Combination of name and age must be unique" }),
  "people[].name": isRequired({ message: "Name is required" }),
});

When I use this combined validator, it is not returning errors for the customNameAndAgeUniqueValidator and only returning errors from the isRequired validator. If I reverse the order they're defined, so

const validator = combineValidators({
  "people[].name": isRequired({ message: "Name is required" }),
  "people[]": customNameAndAgeUniqueValidator({ message: "Combination of name and age must be unique" }),
});

then the reverse happens; the errors from the customNameAndAgeUniqueValidator validator are returned but the ones from isRequired are not.

I was just wondering if you believe this should be supported? I couldn't see any tests around this use case.

I've attached the full code I used to check the behaviour below. P.S. thanks for the library - really enjoyed using it so far.

const revalidate = require("revalidate");
const chai = require("chai")
const expect = chai.expect;

const customNameAndAgeUniqueValidator = revalidate.createValidator(
  (message) => 
    (person, allValues) => { 
      if (!person) {
      	return;
    	}
      if (allValues.people.find(p => p.name === person.name && p.age === person.age && p !== person)) {
      	return message;
      }
  },
  (field) => `${field} must be unique`
);

const validatorA = revalidate.combineValidators({
  "people[]": customNameAndAgeUniqueValidator({ message: "Combination of name and age must be unique" }),
});

const validatorB = revalidate.combineValidators({
  "people[]": customNameAndAgeUniqueValidator({ message: "Combination of name and age must be unique" }),
  "people[].name": revalidate.isRequired({ message: "Name is required" }),
});

const duplicatePeople = {
  people: [
    { name: "Joe", age: 30 },
    { name: "Bill", age: 30 },
    { name: "Joe", age: 30 }
  ]
};
const expectedResultForDuplicatePeople = { 
  people: [
  	"Combination of name and age must be unique",
  	undefined,
  	"Combination of name and age must be unique"
	]
};

// works:
expect(validatorA(duplicatePeople)).to.deep.equal(expectedResultForDuplicatePeople, "Validator A, uniqueness");

// works:
expect(validatorB({ people: [{ name: "" }] })).to.deep.equal({ people: [{ name: "Name is required" }] }, "Validator B, required");

// does not work:
expect(validatorB(duplicatePeople)).to.deep.equal(expectedResultForDuplicatePeople, "Validator B, uniqueness");

console.log("Done!");

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.