Mise à jour: cette réponse est obsolète. Éloignez-vous des mixins si vous le pouvez. Je t'avais prévenu!
Les mixins sont morts. Composition longue vie
Au début, j'ai essayé d'utiliser des sous-composants pour cela et d'extraire FormWidget
et InputWidget
. Cependant, j'ai abandonné cette approche à mi-chemin car je voulais un meilleur contrôle sur les générés input
et leur état.
Deux articles qui m'ont le plus aidé:
Il s'est avéré que je n'avais besoin que d'écrire deux mixins (différents): ValidationMixin
et FormMixin
.
Voici comment je les ai séparés.
ValidationMixin
Validation mixin ajoute des méthodes pratiques pour exécuter vos fonctions de validation sur certaines des propriétés de votre état et stocker les propriétés «d'erreur» dans un state.errors
tableau afin que vous puissiez mettre en évidence les champs correspondants.
define(function () {
'use strict';
var _ = require('underscore');
var ValidationMixin = {
getInitialState: function () {
return {
errors: []
};
},
componentWillMount: function () {
this.assertValidatorsDefined();
},
assertValidatorsDefined: function () {
if (!this.validators) {
throw new Error('ValidatorMixin requires this.validators to be defined on the component.');
}
_.each(_.keys(this.validators), function (key) {
var validator = this.validators[key];
if (!_.has(this.state, key)) {
throw new Error('Key "' + key + '" is defined in this.validators but not present in initial state.');
}
if (!_.isFunction(validator)) {
throw new Error('Validator for key "' + key + '" is not a function.');
}
}, this);
},
hasError: function (key) {
return _.contains(this.state.errors, key);
},
resetError: function (key) {
this.setState({
'errors': _.without(this.state.errors, key)
});
},
validate: function () {
var errors = _.filter(_.keys(this.validators), function (key) {
var validator = this.validators[key],
value = this.state[key];
return !validator(value);
}, this);
this.setState({
'errors': errors
});
return _.isEmpty(errors);
}
};
return ValidationMixin;
});
Usage
ValidationMixin
a trois méthodes: validate
, hasError
et resetError
.
Il s'attend à ce que la classe définisse l' validators
objet, similaire à propTypes
:
var JoinWidget = React.createClass({
mixins: [React.addons.LinkedStateMixin, ValidationMixin, FormMixin],
validators: {
email: Misc.isValidEmail,
name: function (name) {
return name.length > 0;
}
},
// ...
});
Lorsque l'utilisateur appuie sur le bouton de soumission, j'appelle validate
. Un appel à validate
exécutera chaque validateur et remplira this.state.errors
avec un tableau contenant les clés des propriétés dont la validation a échoué.
Dans ma render
méthode, j'utilise hasError
pour générer une classe CSS correcte pour les champs. Lorsque l'utilisateur met le focus dans le champ, j'appelle resetError
pour supprimer la surbrillance d'erreur jusqu'au prochain validate
appel.
renderInput: function (key, options) {
var classSet = {
'Form-control': true,
'Form-control--error': this.hasError(key)
};
return (
<input key={key}
type={options.type}
placeholder={options.placeholder}
className={React.addons.classSet(classSet)}
valueLink={this.linkState(key)}
onFocus={_.partial(this.resetError, key)} />
);
}
FormMixin
Form mixin gère l'état du formulaire (modifiable, soumis, soumis). Vous pouvez l'utiliser pour désactiver les entrées et les boutons pendant l'envoi de la demande, et pour mettre à jour votre vue en conséquence lorsqu'elle est envoyée.
define(function () {
'use strict';
var _ = require('underscore');
var EDITABLE_STATE = 'editable',
SUBMITTING_STATE = 'submitting',
SUBMITTED_STATE = 'submitted';
var FormMixin = {
getInitialState: function () {
return {
formState: EDITABLE_STATE
};
},
componentDidMount: function () {
if (!_.isFunction(this.sendRequest)) {
throw new Error('To use FormMixin, you must implement sendRequest.');
}
},
getFormState: function () {
return this.state.formState;
},
setFormState: function (formState) {
this.setState({
formState: formState
});
},
getFormError: function () {
return this.state.formError;
},
setFormError: function (formError) {
this.setState({
formError: formError
});
},
isFormEditable: function () {
return this.getFormState() === EDITABLE_STATE;
},
isFormSubmitting: function () {
return this.getFormState() === SUBMITTING_STATE;
},
isFormSubmitted: function () {
return this.getFormState() === SUBMITTED_STATE;
},
submitForm: function () {
if (!this.isFormEditable()) {
throw new Error('Form can only be submitted when in editable state.');
}
this.setFormState(SUBMITTING_STATE);
this.setFormError(undefined);
this.sendRequest()
.bind(this)
.then(function () {
this.setFormState(SUBMITTED_STATE);
})
.catch(function (err) {
this.setFormState(EDITABLE_STATE);
this.setFormError(err);
})
.done();
}
};
return FormMixin;
});
Usage
Il s'attend à ce que le composant fournisse une méthode:, sendRequest
qui devrait renvoyer une promesse Bluebird. (Il est simple de le modifier pour qu'il fonctionne avec Q ou une autre bibliothèque de promesses.)
Il fournit des méthodes pratiques telles que isFormEditable
, isFormSubmitting
et isFormSubmitted
. Il fournit également une méthode pour lancer la demande: submitForm
. Vous pouvez l'appeler à partir du onClick
gestionnaire des boutons de formulaire .