简体   繁体   中英

How to compose validation functions

I've been asked to compose the functions that validate data of a form that has inputs for title and description

In the code below I'm making validations using if s but my teacher asked me to convert those if s in functions and compose them.

validate = () => {
    let titleError = '';
    let descriptionError = '';
    const { title, description } = this.state;
    const trimTitle = title.trim();

    if (trimTitle === '') titleError = 'The title cannot be empty';
    if (title.length > 500)
      titleError = 'The title cannot be longer than 500 characters';
    if (description.length > 2000)
      descriptionError =
        'The description cannot be longer than 2000 characters';

    if (titleError !== '' || descriptionError !== '') {
      this.setState({ titleError, descriptionError });
      return false;
    }

    return true;
  };

I've tried to make the functions like these:

emptyTitle = ({ titleError }) => {
    // let titleError = '';
    const trimTitle = title.trim();
    if (trimTitle === '') titleError = 'The title cannot be empty';
    this.setState({ titleError });
  };

  maxTitleLength = ({ title }) => {
    let titleError = '';
    if (title.length > 500)
      titleError = 'The title cannot be longer than 500 characters';
    this.setState({ titleError });
  };

But I'm not sure about my approach. Please, I will really appreciate any suggestion.

Here's one possible implementation using ramda cond method:

 this.state = {title: '', description: ''}; this.setState = (s) => { console.log(s); }; isEmpty = (t) => (t === ''); isLong = (t, n) => (t.length > n); emptyTitle = (t) => isEmpty(t.trim()); longTitle = (t) => isLong(t, 500); longDesc = (d) => isLong(d, 2000); titleValidator = R.cond([ [emptyTitle, R.always('The title cannot be empty')], [longTitle, R.always('The title cannot be longer than 500 characters')], [RT, R.always('')] ]); descValidator = R.cond([ [longDesc, R.always('The description cannot be longer than 2000 characters')], [RT, R.always('')] ]); thereIsError = (e) => (e.title !== '' || e.description !== ''); errorDetected = function (e) { this.setState(e); return false; }; validate = () => { const { title, description } = this.state; const validationError = { title: titleValidator(title), description: descValidator(description) }; const errorLog = R.cond([ [thereIsError, errorDetected], [RT, RT] ]); return errorLog(validationError); }; console.log('Validate:', validate.call(this)); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script> 

I think you have been asked to avoid imperative control flows, R.cond is the functional way of encapsulating if/else logic. Hope it helps.

 const createValidator = (name, { min, max }) => R.cond([ [ // normalize and compare min-length R.pipe(R.trim, R.length, R.gte(min)), // eventually return error message R.always(`${name} cannot be empty`) ], [ // normalize and compare max-length R.pipe(R.trim, R.length, R.lt(max)), // eventually return error message R.always(`${name} too long`) ], // all good, no errors to display [RT, R.always(null)] ]); // The following can be ignored, // only needed as boilerplate to prove the example const Test = () => { const [title, setTitle] = React.useState(''); const [description, setDescription] = React.useState(''); const withValue = (cb) => R.pipe(R.pathOr('', ['target', 'value']), cb); const isTitleValid = createValidator('Title', { min: 0, max: 500 }); const isDescriptionValid = createValidator('Description', { min: 0, max: 2000 }); return ( <form onSubmit={e => e.preventDefault()}> <div className="form-group"> <input className="form-control" placeholder="Title" value={title} onChange={withValue(setTitle)} /> </div> <div className="form-group"> <textarea className="form-control" placeholder="Description" onChange={withValue(setDescription)} value={description} /> </div> <ul className="text-danger"> <li>{isTitleValid(title)}</li> <li>{isDescriptionValid(description)}</li> </ul> </form> ); }; ReactDOM.render(<Test />, document.querySelector('#test')); 
 form { margin: 1em 2em; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script> <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> <div id=test></div> 

If you want to apply all the validation rules and get all the errors, you can normalize the input to be able to accumulate the results:

 /* You can create all validation functions following the pattern: * validationFunc(rule, msg)(normalizedParam): ValidationResult */ const normalizeParam = param => { if (typeof param.errors === 'undefined') { return { value: param, errors: [] } } return param } const makeError = function (param, msg) { return { ...param, errors: param.errors.concat(msg) } } const makeRule = (msg, rule) => param => { const normalizedParam = normalizeParam(param) if (rule(normalizedParam.value)) { return normalizedParam } else { return makeError(normalizedParam, msg) } } const validationGreaterThan = (limit, msg) => makeRule(msg, function(param) { return param > limit }) const validationLesserThan = (limit, msg) => makeRule(msg, function(param) { return param < limit }) const validate = R.compose( validationGreaterThan(10, 'The number must be greater than 10'), validationLesserThan(100, 'The number must be lesser than 100') ) let result = validate(1) if (result.errors.length) { result.errors.forEach(err => console.log(err)) } else { console.log('Success!') } result = validate(101) if (result.errors.length) { result.errors.forEach(err => console.log(err)) } else { console.log('Success!') } result = validate(20) if (result.errors.length) { result.errors.forEach(err => console.log(err)) } else { console.log('Success!') } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script> 

But if you want to cut the validation process, you can throw an exception when an error is found (each validation throws an error or returns the parameter if all went ok).

You can investigate other approaches here:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM