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 FormWidgetet InputWidget. Cependant, j'ai abandonné cette approche à mi-chemin car je voulais un meilleur contrôle sur les générés inputet 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): ValidationMixinet 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.errorstableau 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
ValidationMixina trois méthodes: validate, hasErroret resetError.
Il s'attend à ce que la classe définisse l' validatorsobjet, 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 à validateexécutera chaque validateur et remplira this.state.errorsavec un tableau contenant les clés des propriétés dont la validation a échoué.
Dans ma renderméthode, j'utilise hasErrorpour générer une classe CSS correcte pour les champs. Lorsque l'utilisateur met le focus dans le champ, j'appelle resetErrorpour supprimer la surbrillance d'erreur jusqu'au prochain validateappel.
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:, sendRequestqui 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, isFormSubmittinget isFormSubmitted. Il fournit également une méthode pour lancer la demande: submitForm. Vous pouvez l'appeler à partir du onClickgestionnaire des boutons de formulaire .