Aggiornamento: questa risposta è obsoleta. Stai lontano dai mixin se puoi. Ti ho avvertito!
I mixin sono morti. Lunga vita alla composizione
All'inizio, ho provato a utilizzare i sottocomponenti per questo ed estrarre FormWidget
e InputWidget
. Tuttavia, ho abbandonato a metà questo approccio perché volevo un controllo migliore sui messaggi generati input
e sul loro stato.
Due articoli che mi hanno aiutato di più:
Si è scoperto che avevo solo bisogno di scrivere due (diversi) mixin: ValidationMixin
e FormMixin
.
Ecco come li ho separati.
ValidationMixin
Il mixin di convalida aggiunge metodi di convenienza per eseguire le funzioni di convalida su alcune delle proprietà del tuo stato e memorizzare le proprietà "errate" in un state.errors
array in modo da poter evidenziare i campi corrispondenti.
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
ha tre metodi: validate
, hasError
e resetError
.
Si aspetta che la classe definisca l' validators
oggetto, simile a propTypes
:
var JoinWidget = React.createClass({
mixins: [React.addons.LinkedStateMixin, ValidationMixin, FormMixin],
validators: {
email: Misc.isValidEmail,
name: function (name) {
return name.length > 0;
}
},
// ...
});
Quando l'utente preme il pulsante di invio, chiamo validate
. Una chiamata a validate
eseguirà ogni validatore e popolerà this.state.errors
un array che contiene le chiavi delle proprietà che hanno fallito la convalida.
Nel mio render
metodo, utilizzo hasError
per generare la classe CSS corretta per i campi. Quando l'utente mette il focus all'interno del campo, chiamo resetError
per rimuovere l'evidenziazione dell'errore fino alla validate
chiamata successiva .
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
Il mixin del modulo gestisce lo stato del modulo (modificabile, in fase di invio, inviato). È possibile utilizzarlo per disabilitare input e pulsanti durante l'invio della richiesta e per aggiornare la visualizzazione in modo corrispondente quando viene inviata.
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
Si aspetta che il componente fornisca un metodo:, sendRequest
che dovrebbe restituire una promessa Bluebird. (È banale modificarlo per funzionare con Q o altre librerie di promesse.)
Fornisce metodi convenienti come isFormEditable
, isFormSubmitting
e isFormSubmitted
. Esso fornisce anche un metodo per dare il via alla richiesta: submitForm
. Puoi chiamarlo dal onClick
gestore dei pulsanti del modulo .