Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 ifs but my teacher asked me to convert those ifs 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.

like image 853
Delm Avatar asked Apr 26 '26 02:04

Delm


2 Answers

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
  [R.T, 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>
like image 81
Hitmands Avatar answered Apr 28 '26 15:04

Hitmands


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:

  • See: https://codeburst.io/composable-object-validations-in-js-efa1ebacc34e
  • See conceptos of predicates and rules: https://medium.com/javascript-inside/form-validation-as-a-higher-order-component-pt-1-83ac8fd6c1f0
  • See Validation Functor: https://www.freecodecamp.org/news/functional-programming-in-js-with-practical-examples-part-2-429d2e8ccc9e/
like image 43
Mauro Stepanoski Avatar answered Apr 28 '26 17:04

Mauro Stepanoski



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!