I've been applying a Flux-like architecture to my React app, and am wondering if the app state is really meant to be kept in the store(s) rather than components. It seems like there are some scenarios where it might be good to have some components maintain their own state.
For instance, the below self-aware FormElement
. I couldn't imagine writing that with a Flux-like architecture, where everything is dispatched to the store as an action. How would the store possibly keep track of all the different form elements, and their parent form?
In short: is it acceptable to have some components keep track of their own state, while most others use dispatch?
FormElement = React.createClass({
displayName: 'FormElement',
validations: {
email: /^[A-Za-z0-9-._+]+@[A-Za-z0-9-]+[.A-Za-z0-9-]*\.[A-Za-z]{2,}$/,
password: /.{6,}/
},
propTypes: {
id: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired,
type: React.PropTypes.string,
required: React.PropTypes.bool
},
getDefaultProps() {
return {
type: 'text',
required: false,
disabled: false
}
},
getInitialState() {
return {
focused: false,
filled: false,
valid: true,
invalidMessage: ''
}
},
handleFocus(focusing) {
let valid = true, errMsg = '';
let inputVal = React.findDOMNode(this.refs.inputField).value;
this.setState({focused: focusing});
if (!focusing) {
// Do some validations on blur
if (this.props.required && !this.state.filled) {
valid = false;
errMsg = this.props.label + ' is required';
}
if (this.props.type === 'email' &&
this.state.filled && !this.validFormat(inputVal, 'email')) {
valid = false;
errMsg = 'Invalid email address';
} else if (this.props.type === 'password' &&
this.state.filled && !this.validFormat(inputVal, 'password')) {
valid = false;
errMsg = 'Password too short';
}
}
this.setState({valid, invalidMessage: errMsg}, function () {
// Notify parent that something changed
//this.props.onAction(this);
});
},
handleChange({target}) {
this.setState({
value: target.value,
filled: target.value.length > 0
});
},
validFormat(str, type) {
return !!str.match(this.validations[type]);
},
render() {
let formElement;
const labelClasses = classNames({
'focused': this.state.focused || this.state.filled
});
const groupClasses = classNames({
'form-group': true,
'has-error': !this.state.valid
});
if (_.contains(['text', 'email', 'password'], this.props.type)) {
formElement = (
<div className={groupClasses}>
<label
className={labelClasses}
htmlFor={this.props.id}>
{this.state.invalidMessage ?
this.state.invalidMessage : this.props.label}
</label>
<input type={this.props.type}
className="form-control"
id={this.props.id}
ref="inputField"
onFocus={this.handleFocus.bind(null, true)}
onBlur={this.handleFocus.bind(null, false)}
onChange={this.handleChange}
disabled={this.props.disabled} />
</div>
);
} else if (this.props.type === 'submit') {
formElement = (
<div>
<button type="submit"
className="btn btn-primary"
disabled={this.props.disabled}>{this.props.label}
</button>
</div>
);
}
return formElement;
}
});