Juste une autre chose pour ceux qui veulent créer un composant de formulaire entièrement contrôlé sans utiliser de bibliothèque surdimensionnée.
ReduxFormHelper - une petite classe ES6, moins de 100 lignes:
class ReduxFormHelper {
constructor(props = {}) {
let {formModel, onUpdateForm} = props
this.props = typeof formModel === 'object' &&
typeof onUpdateForm === 'function' && {formModel, onUpdateForm}
}
resetForm (defaults = {}) {
if (!this.props) return false
let {formModel, onUpdateForm} = this.props
let data = {}, errors = {_flag: false}
for (let name in formModel) {
data[name] = name in defaults? defaults[name] :
('default' in formModel[name]? formModel[name].default : '')
errors[name] = false
}
onUpdateForm(data, errors)
}
processField (event) {
if (!this.props || !event.target) return false
let {formModel, onUpdateForm} = this.props
let {name, value, error, within} = this._processField(event.target, formModel)
let data = {}, errors = {_flag: false}
if (name) {
value !== false && within && (data[name] = value)
errors[name] = error
}
onUpdateForm(data, errors)
return !error && data
}
processForm (event) {
if (!this.props || !event.target) return false
let form = event.target
if (!form || !form.elements) return false
let fields = form.elements
let {formModel, onUpdateForm} = this.props
let data = {}, errors = {}, ret = {}, flag = false
for (let n = fields.length, i = 0; i < n; i++) {
let {name, value, error, within} = this._processField(fields[i], formModel)
if (name) {
value !== false && within && (data[name] = value)
value !== false && !error && (ret[name] = value)
errors[name] = error
error && (flag = true)
}
}
errors._flag = flag
onUpdateForm(data, errors)
return !flag && ret
}
_processField (field, formModel) {
if (!field || !field.name || !('value' in field))
return {name: false, value: false, error: false, within: false}
let name = field.name
let value = field.value
if (!formModel || !formModel[name])
return {name, value, error: false, within: false}
let model = formModel[name]
if (model.required && value === '')
return {name, value, error: 'missing', within: true}
if (model.validate && value !== '') {
let fn = model.validate
if (typeof fn === 'function' && !fn(value))
return {name, value, error: 'invalid', within: true}
}
if (model.numeric && isNaN(value = Number(value)))
return {name, value: 0, error: 'invalid', within: true}
return {name, value, error: false, within: true}
}
}
Cela ne fait pas tout le travail pour vous. Cependant, il facilite la création, la validation et la gestion d'un composant de formulaire contrôlé. Vous pouvez simplement copier et coller le code ci-dessus dans votre projet ou à la place, inclure la bibliothèque respective - redux-form-helper
(plug!).
Comment utiliser
La première étape consiste à ajouter des données spécifiques à l'état Redux qui représenteront l'état de notre formulaire. Ces données comprendront les valeurs de champ actuelles ainsi qu'un ensemble d'indicateurs d'erreur pour chaque champ du formulaire.
L'état du formulaire peut être ajouté à un réducteur existant ou défini dans un réducteur séparé.
De plus, il est nécessaire de définir une action spécifique déclenchant la mise à jour de l'état du formulaire ainsi que le créateur d'action respectif.
Exemple d'action :
export const FORM_UPDATE = 'FORM_UPDATE'
export const doFormUpdate = (data, errors) => {
return { type: FORM_UPDATE, data, errors }
}
...
Exemple de réducteur :
...
const initialState = {
formData: {
field1: '',
...
},
formErrors: {
},
...
}
export default function reducer (state = initialState, action) {
switch (action.type) {
case FORM_UPDATE:
return {
...ret,
formData: Object.assign({}, formData, action.data || {}),
formErrors: Object.assign({}, formErrors, action.errors || {})
}
...
}
}
La deuxième et dernière étape consiste à créer un composant conteneur pour notre formulaire et à le connecter avec une partie respective de l'état et des actions Redux.
Nous devons également définir un modèle de formulaire spécifiant la validation des champs de formulaire. Maintenant, nous instancions ReduxFormHelper
object en tant que membre du composant et y passons notre modèle de formulaire et une mise à jour de distribution de rappel de l'état du formulaire.
Ensuite, dans la render()
méthode du composant, nous devons lier onChange
les onSubmit
événements de chaque champ et du formulaire avec les méthodes processField()
et processForm()
respectivement, ainsi que d'afficher des blocs d'erreur pour chaque champ en fonction des indicateurs d'erreur de formulaire dans l'état.
L'exemple ci-dessous utilise le CSS du framework Twitter Bootstrap.
Exemple de composant de conteneur :
import React, {Component} from 'react';
import {connect} from 'react-redux'
import ReduxFormHelper from 'redux-form-helper'
class MyForm extends Component {
constructor(props) {
super(props);
this.helper = new ReduxFormHelper(props)
this.helper.resetForm();
}
onChange(e) {
this.helper.processField(e)
}
onSubmit(e) {
e.preventDefault()
let {onSubmitForm} = this.props
let ret = this.helper.processForm(e)
ret && onSubmitForm(ret)
}
render() {
let {formData, formErrors} = this.props
return (
<div>
{!!formErrors._flag &&
<div className="alert" role="alert">
Form has one or more errors.
</div>
}
<form onSubmit={this.onSubmit.bind(this)} >
<div className={'form-group' + (formErrors['field1']? ' has-error': '')}>
<label>Field 1 *</label>
<input type="text" name="field1" value={formData.field1} onChange={this.onChange.bind(this)} className="form-control" />
{!!formErrors['field1'] &&
<span className="help-block">
{formErrors['field1'] === 'invalid'? 'Must be a string of 2-50 characters' : 'Required field'}
</span>
}
</div>
...
<button type="submit" className="btn btn-default">Submit</button>
</form>
</div>
)
}
}
const formModel = {
field1: {
required: true,
validate: (value) => value.length >= 2 && value.length <= 50
},
...
}
function mapStateToProps (state) {
return {
formData: state.formData, formErrors: state.formErrors,
formModel
}
}
function mapDispatchToProps (dispatch) {
return {
onUpdateForm: (data, errors) => {
dispatch(doFormUpdate(data, errors))
},
onSubmitForm: (data) => {
// dispatch some action which somehow updates state with form data
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyForm)
Démo
redux-forms
? Je me demande comment ce passe-partout passe par rapport aux formes de réaction