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 FormWidget
y InputWidget
. Sin embargo, abandoné este enfoque a la mitad porque quería un mejor control sobre los mensajes generados input
y su estado.
Dos artículos que más me ayudaron:
Resultó que solo necesitaba escribir dos mixins (diferentes): ValidationMixin
y 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.errors
matriz 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
ValidationMixin
tiene tres métodos: validate
, hasError
y resetError
.
Espera que la clase defina el validators
objeto, 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 validate
ejecutará cada validador y se completará this.state.errors
con una matriz que contiene claves de las propiedades que fallaron en la validación.
En mi render
método, utilizo hasError
para generar la clase CSS correcta para los campos. Cuando el usuario pone el foco dentro del campo, llamo resetError
para eliminar el resaltado de error hasta la próxima validate
llamada.
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:, sendRequest
que 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
, isFormSubmitting
y isFormSubmitted
. También proporciona un método para dar inicio a la solicitud: submitForm
. Puede llamarlo desde el onClick
controlador de botones de formulario .