Utilisation de formulaires dans React.js Ă  l'aide d'outils de base

Présentation


En travaillant sur React.js, je devais souvent m'occuper du traitement des formulaires. Redux-Form , React-Redux-Form est passé entre mes mains, mais aucune des bibliothèques ne m'a complètement satisfait. Je n'aimais pas que l'état du formulaire soit stocké dans le réducteur , et chaque événement passe par le créateur d'action . De plus, selon Dan Abramov, "l'état du formulaire est intrinsèquement éphémère et local, vous n'avez donc pas besoin de le suivre dans Redux (ou dans n'importe quelle bibliothèque Flux)".


Je note que dans React-Redux-Form, il existe un composant LocalForm qui vous permet de travailler sans redux, mais à mon avis, cela n'a aucun sens d'installer une bibliothèque de 21,9 Ko et de l'utiliser moins de la moitié.


Je ne suis pas contre les bibliothèques nommées, dans des cas spécifiques, elles sont irremplaçables. Par exemple, lorsqu'un composant tiers non lié au formulaire dépend des données saisies. Mais dans mon article, je veux considérer les formulaires qui n'ont pas besoin de redux.


J'ai commencé à utiliser l' état local du composant, et de nouvelles difficultés sont apparues: la quantité de code a augmenté, les composants ont perdu leur lisibilité, de nombreuses duplications sont apparues.


La solution était le concept de High Order Component. En bref, HOC est une fonction qui reçoit une entrée de composant et la renvoie mise à jour avec l'intégration d'accessoires supplémentaires ou modifiés. En savoir plus sur HOC sur le site officiel de React.js . Le but de l'utilisation du concept HOC était de diviser le composant en deux parties, dont l'une serait responsable de la logique et la seconde - de l'affichage.


Création de formulaire


A titre d'exemple, nous allons créer un simple formulaire de feedback dans lequel il y aura 3 champs: nom, email, téléphone.


Pour plus de simplicité, nous utilisons Create-React-App . Installez-le globalement:


npm i -g create-react-app 

puis créez votre application dans le dossier pure-form


 create-react-app pure-form 

De plus, installez les prop-types et les noms de classe , ils nous seront utiles Ă  l'avenir:


 npm i prop-types classnames -S 

Créez deux dossiers / composants et / conteneurs . Le dossier / components contiendra tous les composants responsables de l'affichage. Dans le dossier / containers , les composants responsables de la logique.


Dans le dossier / components , créez un fichier Input.jsx dans lequel nous déclarons un composant commun pour toutes les entrées. Il est important à ce stade de ne pas oublier de prescrire des ProptTypes et des defaultProps de manière qualitative, de prévoir la possibilité d'ajouter des classes personnalisées, et aussi de l'hériter de PureComponent pour l'optimisation.
Le résultat est:


 import React, { PureComponent } from 'react'; import cx from 'classnames'; import PropTypes from 'prop-types'; class Input extends PureComponent { render() { const { name, error, labelClass, inputClass, placeholder, ...props } = this.props; return ( <label className={cx('label', !!labelClass && labelClass)} htmlFor={`id-${name}`} > <span className="span">{placeholder}</span> <input className={cx( 'input', !!inputClass && inputClass, !!error && 'error' )} name={name} id={`id-${name}`} onFocus={this.handleFocus} onBlur={this.handleBlur} {...props} /> {!!error && <span className="errorText">{error}</span>} </label> ); } } Input.defaultProps = { type: 'text', error: '', required: false, autoComplete: 'off', labelClass: '', inputClass: '', }; Input.propTypes = { value: PropTypes.string.isRequired, name: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, placeholder: PropTypes.string.isRequired, error: PropTypes.string, type: PropTypes.string, required: PropTypes.bool, autoComplete: PropTypes.string, labelClass: PropTypes.string, inputClass: PropTypes.string, }; export default Input; 

Ensuite, dans le dossier / components , créez le fichier Form.jsx , dans lequel le composant contenant le formulaire sera déclaré. Nous recevrons toutes les méthodes pour travailler avec lui via des accessoires, ainsi que la valeur des entrées, donc l' état n'est pas nécessaire ici. Nous obtenons:


 import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Input from './Input'; import FormWrapper from '../containers/FormWrapper'; class Form extends Component { render() { const { data: { username, email, phone }, errors, handleInput, handleSubmit, } = this.props; return ( <div className="openBill"> <form className="openBillForm" onSubmit={handleSubmit}> <Input key="username" value={username} name="username" onChange={handleInput} placeholder="" error={errors.username} required /> <Input key="phone" value={phone} name="phone" onChange={handleInput} placeholder="" error={errors.phone} required /> <Input key="email" value={email} type="email" name="email" onChange={handleInput} placeholder=" " error={errors.email} required /> <button type="submit" className="submitBtn">   </button> </form> </div> ); } } Form.propTypes = { data: PropTypes.shape({ username: PropTypes.string.isRequired, phone: PropTypes.string.isRequired, email: PropTypes.string.isRequired, }).isRequired, errors: PropTypes.shape({ username: PropTypes.string.isRequired, phone: PropTypes.string.isRequired, email: PropTypes.string.isRequired, }).isRequired, handleInput: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired, }; export default FormWrapper(Form); 

Création HOC


Dans le dossier / containers , créez le fichier FormWrapper.jsx . Nous déclarons la fonction à l'intérieur, qui prend le composant WrappedComponent comme argument et renvoie la classe WrappedForm . La méthode de rendu de cette classe renvoie un WrappedComponent avec des accessoires intégrés. Essayez d'utiliser la déclaration de fonction classique, cela simplifiera le processus de débogage.


Dans la classe WrappedForm, créez l' état : isFetching - un indicateur pour contrôler les demandes asynchrones, les données - un objet avec des valeurs d'entrée, des erreurs - un objet pour stocker les erreurs. L' état déclaré est transmis au composant WrappedComponent . Ainsi, l'implémentation du stockage des états de formulaire au niveau supérieur est implémentée, ce qui rend le code plus lisible et transparent.


 export default function Wrapper(WrappedComponent) { return class FormWrapper extends Component { state = { isFetching: false, data: { username: '', phone: '', email: '', }, errors: { username: '', phone: '', email: '', }, }; render() { return <WrappedComponent {...this.state} />; } }; } 

Mais une telle implémentation n'est pas universelle, car pour chaque formulaire, vous devez créer votre propre wrapper. Vous pouvez améliorer ce système et intégrer le HOC dans une autre fonction qui formera les valeurs d'état initiales.


 import React, { Component } from 'react'; export default function getDefaultValues(initialState, requiredFields) { return function Wrapper(WrappedComponent) { return class WrappedForm extends Component { state = { isFetching: false, data: initialState, errors: requiredFields, }; render() { return <WrappedComponent {...this.state} {...this.props} />; } }; }; } 

Dans cette fonction, vous pouvez non seulement transmettre les valeurs initiales de l' état , mais généralement tous les paramètres. Par exemple, des attributs et des méthodes sur la base desquels il sera possible de créer un formulaire dans Form.jsx . Un exemple d'une telle implémentation sera le sujet du prochain article.


Dans le fichier Form.jsx, nous déclarons les valeurs d' état initiales et les transmettons au HOC:


 const initialState = {    username: '',    phone: '',    email: '', }; export default FormWrapper(initialState, initialState)(Form); 

Créons la méthode handleInput pour traiter les valeurs entrées dans l'entrée. Il reçoit un événement dont nous prenons la valeur et le nom et nous les transférons à setState . Puisque les valeurs d'entrée sont stockées dans l'objet de données , nous appelons la fonction dans setState . Simultanément à la sauvegarde de la valeur obtenue, on remet à zéro le stockage d'erreur du champ variable. Nous obtenons:


 handleInput = event => { const { value, name } = event.currentTarget; this.setState(({ data, errors }) => ({ data: { ...data, [name]: value, }, errors: { ...errors, [name]: '', }, })); }; 

Nous allons maintenant créer la méthode handeSubmit pour traiter le formulaire et afficher les données dans la console, mais avant cela, nous devons passer la validation. Nous validerons uniquement les champs obligatoires, c'est-à-dire toutes les clés de l'objet this.state.errors. Nous obtenons:


 handleSubmit = e => {    e.preventDefault();    const { data } = this.state;    const isValid = Object.keys(data).reduce( (sum, item) => sum && this.validate(item, data[item]),    true    ); if (isValid) { console.log(data); } }; 

En utilisant la méthode de réduction, nous trions tous les champs obligatoires. À chaque itération, la méthode de validation est appelée, dans laquelle nous transmettons une paire de nom , valeur . À l'intérieur de la méthode, une vérification est effectuée pour l'exactitude des données entrées, dont les résultats renvoient le type booléen. Si au moins une paire de valeurs ne passe pas la validation, la variable isValid deviendra fausse et les données ne seront pas sorties vers la console, c'est-à-dire que le formulaire ne sera pas traité. Un cas simple est considéré ici - une vérification pour un formulaire non vide. Valider la méthode:



 validate = (name, value) => { if (!value.trim()) { this.setState( ({ errors }) => ({ errors: { ...errors, [name]: '    ', }, }), () => false ); } else { return true; } }; 

Les méthodes handleSubmit et handleInput doivent être transmises au WrappedComponent :


 render() {    return (    <WrappedComponent    {...this.state}       {...this.props}     handleInput={this.handleInput} handleSubmit={this.handleSubmit}    /> ); } 

En conséquence, nous obtenons un formulaire de rétroaction prêt à l'emploi, avec une validation simple et une sortie d'erreur. Dans le même temps, nous avons supprimé la partie logique du composant responsable de l'affichage.


Conclusion


Nous avons donc examiné un exemple de base de création d'un HOC pour le traitement des formulaires. Lors de la création du formulaire, seules des entrées simples ont été utilisées, sans éléments complexes, tels que des listes déroulantes, des cases à cocher, des boutons radio et autres. Si disponible, vous devrez peut-être créer des méthodes de traitement d'événements supplémentaires.


Écrivez vos questions et commentaires dans les commentaires de l'article ou de mon courrier.

Un exemple complet peut être trouvé ici: forme de réaction pure .

Merci de votre attention!

Source: https://habr.com/ru/post/fr421817/


All Articles