Actualización: esta respuesta está desactualizada. Manténgase alejado de los mixins si puede. ¡Te lo adverti!
Los mixins están muertos. Composición Larga Vida
Al principio, traté de usar subcomponentes para esto y extraer FormWidgety InputWidget. Sin embargo, abandoné este enfoque a la mitad porque quería un mejor control sobre los mensajes generados inputy su estado.
Dos artículos que más me ayudaron:
Resultó que solo necesitaba escribir dos mixins (diferentes): ValidationMixiny FormMixin.
Así es como los separé.
ValidaciónMixin
Validation mixin agrega métodos convenientes para ejecutar sus funciones de validación en algunas de las propiedades de su estado y almacenar propiedades "con error" en una state.errorsmatriz para que pueda resaltar los campos correspondientes.
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;
});
Uso
ValidationMixintiene tres métodos: validate, hasErrory resetError.
Espera que la clase defina el validatorsobjeto, similar a propTypes:
var JoinWidget = React.createClass({
mixins: [React.addons.LinkedStateMixin, ValidationMixin, FormMixin],
validators: {
email: Misc.isValidEmail,
name: function (name) {
return name.length > 0;
}
},
// ...
});
Cuando el usuario presiona el botón de envío, llamo validate. Una llamada a validateejecutará cada validador y se completará this.state.errorscon una matriz que contiene claves de las propiedades que fallaron en la validación.
En mi rendermétodo, utilizo hasErrorpara generar la clase CSS correcta para los campos. Cuando el usuario pone el foco dentro del campo, llamo resetErrorpara eliminar el resaltado de error hasta la próxima validatellamada.
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 maneja el estado del formulario (editable, envío, enviado). Puede usarlo para deshabilitar entradas y botones mientras se envía la solicitud, y para actualizar su vista en consecuencia cuando se envía.
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;
});
Uso
Espera que el componente proporcione un método:, sendRequestque debería devolver una promesa Bluebird. (Es trivial modificarlo para que funcione con Q u otra biblioteca de promesas).
Proporciona métodos de conveniencia como isFormEditable, isFormSubmittingy isFormSubmitted. También proporciona un método para dar inicio a la solicitud: submitForm. Puede llamarlo desde el onClickcontrolador de botones de formulario .