npm package: @knfcz/react-utils
Description: ## Validation de formulaires
Author: knfcz
Latest version: 1.0.0

# react-utils

## Validation de formulaires

Dans ./formValidation/ se trouvent des fonctions à utiliser pour valider rapidement des formulaires avec react

> makeValidator({object} options) =>  {function} validate

> validate({object} validationRules, [{object} state, {function} setState]) => {boolean} formValid

Utilisez makeValidator() en lui filant vos options pour créer votre propre fonction de validation à utiliser dans vos formulaires

### Options du validateur:

> getErrorMessage: function({string} errorName, {object} errorMessageParameters) => {string} errorMessage

Reçoit le nom et les paramètres d'une règle ayant échoué et renvoie un message d'erreur à afficher dans le formulaire

### Configuration du validateur

Dans un fichier à part, exportez la fonction renvoyée par makeValidator()

```js
// validate.js
import { makeValidator } from 'js-utils/formValidation';

const options = {
    getErrorMessage: (errorName, errorMessageParameters) =>
        translate(`form.validation.errors.${errorName}`, errorMessageParameters),
};

export default makeValidator(options);
```

### Utilisation

Définissez les règles de validation de vos champs dans un objet, des règles prêtes à être utilisées sont dans ./formValidation/rules.js

```js
// UserForm.js
import { rules } from 'js-utils/formValidation';
...
const validationRules = {
    firstName: [rules.required(), rules.lengthBetween(2, 20)]
    lastName: [rules.required(), rules.lengthBetween(2, 20)]
    email: [
        rules.required(),

        // Vous pouvez passer des options supplémentaires
        // en déclarant votre règle dans un objet au format {rule, ...options}
         {
            rule: rules.isValidEmail(),
            getErrorMessage: (errorName, errorMessage) => {
            // Si la regle isValidEmail() échoue, cette fonction sera utilisée pour générer le message d'erreur

            return "Message d'erreur personnalisé"
        }
    }]
};
...
```


Créez le state de votre formulaire en passant un objet contenant les valeurs par defaut de vos champs à createFormInitialState()

```js
// UserForm.js
import { createFormInitialState } from 'js-utils/formValidation';

...
const [formState, setFormState] = useSetState(
    createFormInitialState({
        firstName: 'Michel',
        lastName: 'Smith',
        email: 'supermichel@hotmail.fr',
    })
);
...
```

Puis passez les valeurs et les messages d'erreur de vos champs à vos composants TextInput, etc...

```js
// UserForm.js
...
<TextInput
    value={formState.firstName.value}
    errorMessage={formState.firstName.error}
    ...
/>
...
```

Pour valider le formulaire, vous n'avez qu'a appeler votre fonction validate() crée plus haut,
si des champs sont invalides, les messages d'erreurs associés seront enregistrés dans le state et vos composants seront mis à jour

```js
// UserForm.js
import { getFormValues } from 'js-utils/formValidation';
...
const onSubmit = async () => {
    if(!validate(validationRules, [formState, setFormState])) {
        // Formulaire invalide

        return;
    }

    // Formulaire bueno

    // Note: Pour récupérer les données du form dans un objet au format {nomChamp: valeurChamp},
    // passez le state de votre formulaire à getFormValues();
    await Http.post('https://michel3000.com/users/', getFormValues(formState))
}
...
```

### Créer de nouvelles règles de validation

Les règles de validation reçoivent une valeur à tester et renvoient un tableau au format [nomErreur, parametresMessageErreur],
si la valeur est valide, nomErreur devra être false et parametresMessageErreur pourra être omis

Note: Pensez à accepter une valeur vide comme valide, sinon les champs testés avec votre règle ne pourront être laissés vide

Exemple:

```js
export const minLength = min => val => {
   if(val !== '' && val.length < min) {
       return ['minLength', { min }];
   }

   return [false];
}

export const minLengthForUsername = minLength(2);
export const minLengthForEmail = minLength(6);
```

Note: Renvoyer une [closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) récupérant la valeur à tester en argument permettra aux autre de créer facilement des versions plus spécialisées de vos règles, et rendra la définition des règles de validation plus lisible

Example avec et sans closure

```js
// validation.js

export const validEmail = () => val => {
   ...
}

export const minLength = (min, val) => {
    ...
}

export const curriedMinLength = min => val => {
    ...
}

// SomeForm.js

...

const validationRules = {
    email: [validEmail()]
    label: [
        // Si nous souhaitons passer des arguments autres que la valeur à tester
        // à notre règle de validation, nous devrons créer une fonction intermédiaire
        // qui recevra la valeur et appelera notre règle en lui passant les arguments supplémentaires
        val => minLength(2, val),

        // Ce qui est équivalent à
        curriedMinLength(2),
    ]
};

...

```

### Conseils et performance

Par soucis de convention et de rétrocompatibilité (au cas où des arguments seraient ajoutés ultérieurement)
les règles de validations ne récupèrent jamais la valeur à tester et doivent renvoyer une closure qui sera appelée avec la valeur en argument,
en contrepartie cela peu mener à la création d'un grand nombre de fonctions si vos règles de validation sont souvent regénérées.

```js
// ChristianHipHopGroupAdmissionForm.js

// Ici, chaque appel à getValidationRules() génerera six closures, soit une pour chaque règle,
const getValidationRules = formValues => ({
    age: [rules.required(), rules.numberBetween(12, 99)],
    name: [rules.required(), rules.numberBetween(12, 99)],
    parentName: [rules.requiredIf(formValues.age < 18), rules.lengthBetween(2, 20)],
    ...
});

```

Il peut être donc intéressant de créer votre propre fichier de validation dans votre projet,
afin d'y créer vos règles et de leur fournir éventuellement des arguments prédéfinis

```js
// src/utils/validation.js
import { rules as defaultRules } from 'js-utils/formValidation';

export const rules = {
    ...defaultRules,

    // Vous pouvez créer required et toutes les autres règles
    // ne prenant aucun argument ici une bonne fois pour toutes
    required: defaultRules.required(),
    validEmail: defaultRules.validEmail(),

    // Et facilement créer des règles génériques
    validNameLength: defaultRules.lengthBetween(2, 20),
    validAge: defaultRules.numberBetween(12, 99),
    ...
};

...

// ChristianHipHopGroupAdmissionForm.js
import { rules } from 'utils/validation';

// Ici, aucune fonction n'est crée
const getValidationRules = formValues => ({
    age: [rules.required, rules.validAge],
    name: [rules.required, rules.validNameLength],
    parentName: [rules.requiredIf(formValues.age < 18), rules.validNameLength],
    ...
});

...
```