ELEGANT TWO WAY DATA BINDING WITH REACT MIXINS
Christopher Davis has written this article. More details coming soon.
React, a framework for the view part of your JS application, doesn’t supply two way data binding by default. It expects that your data flows downward from your model (whatever that is) to the view (react component). So say you want part of a form field to update another part of an application: you’ll have to write an onChange handler for that field and update the component’s state. It looks something like this.
simple.jsx
var React = require('react');
var SimpleHello = React.createClass({ getInitialState: function () { return {name: this.props.initialName}; }, onChange: function (event) { if (this.state.name !== event.target.value) { this.setState({ name: event.target.value }); } }, render: function () { return <div> <h2>Hello From Simple, {this.state.name}</h2> <input type="text" value={this.state.name} onChange={this.onChange} /> </div> } });
Simple right? It’s not hard to understand, but it’s definitely verbose. I definitely don’t want to write a bunch of onChange handlers for every component I write.
This sort of simple on change binding to state is somewhat of a cross cutting concern, so we can write a mixin to accomplish the same thing in a re-usable way. The mixin itself is simple: we’ll supply a single method that returns an object with two keys: value, for the value of the field, and onChange that will be our callback function for the onChange event. The names of these keys are important here (we’ll see why a bit further down).
StateTwoWayMixin.js
var StateTwoWayMixin = {
linkState: function (key) {
return {
value: this.state[key],
onChange: function (event) {
if (event.target.value === this.state[key]) {
return;
}
var newState = {};
newState[key] = event.target.value;
this.setState(newState);
}.bind(this)
}
}
};
Now our component will include the new mixin and use its linkState method with the JSX spread operator. The spread operator just copies the keys from the object into the child component’s properties. We named our keys value and onChange so they could play nice with inputs.
state.jsx
Now we can reuse this simple input to state linking across objects.var StateHello = React.createClass({
mixins: [StateTwoWayMixin], getInitialState: function () { return {name: this.props.initialName}; }, render: function () { return <div> <h2>Hello From State, {this.state.name}</h2> <input type="text" {...this.linkState('name')} /> </div> } });
A pretty common pattern we’ve run into is passing some sort of model object into a component via it’s properties when rendering:
ReactDOM.render(<SomeComponent model={someObject} />, document.getElementById('something'));
To do two way data binding for something like this, we might write…
simpleobject.jsx
var React = require('react');
var SimpleObjectHello = React.createClass({ onChange: function (event) { if (this.props.model.name !== event.target.value) { this.props.model.name = event.target.value; this.forceUpdate(); } }, render: function () { return <div> <h2>Hello From Simple Object, {this.props.model.name}</h2> <input type="text" value={this.props.model.name} onChange={this.onChange} /> </div> } });
Not too much different from the first example above. We update the object in the onChange callback, but we have to call forceUpdate since react doesn’t know values within model changed.
Let’s pull that out into a mixin.ObjectTwoWayMixin.js
var ObjectTwoWayMixin = {
linkObject: function (object, objectKey) { return { value: object[objectKey], onChange: function (event) { if (event.target.value !== object[objectKey]) { object[objectKey] = event.target.value; this.forceUpdate(); } }.bind(this) } } };
And that mixin can be re-used in components with the spread operator just like above.
object.jsx
ObjectHello = React.createClass({
mixins: [ObjectTwoWayMixin], render: function () { return <div> <h2>Hello From Object, {this.props.model.name}</h2> <input type="text" {...this.linkObject(this.props.model, 'name')} /> </div> } });
Which brings us to something like backbone. As you might imagine, a mixin for linking a backbone model looks pretty similar to the object mixin above.
BackboneTwoWayMixin.js
var BackboneTwoWayMixin = {
linkModel: function (model, key) { return { value: model.get(key), onChange: function (event) { if (model.get(key) !== event.target.value) { model.set(key, event.target.value); } }.bind(this) }; } };
Instead of dealing with object properties directly, we use the models get and set methods. Using it looks the same as the examples above. Note that instead of calling forceUpdate in the mixin directly, we use the react.backbone mixin to do that work for us whenever the model changes.
backbone.jsx
var React = require('react');
require('react.backbone'); // sets up `React.BackboneMixin` var BackboneHello = React.createClass({ mixins: [ BackboneTwoWayMixin, React.BackboneMixin('model') ], render: function () { return <div> <h2>Hello From Backbone, {this.props.model.get('name')}</h2> <input type="text" {...this.linkModel(this.props.model, 'name')} /> </div> } });
React has some utilities for two way data binding that use properties called valueLink. ValueLink is deprecated in master. This is an attempt to get the same two way binding sugar in a more future proof way.
Stay in touch
Subscribe to our newsletter
By clicking and subscribing, you agree to our Terms of Service and Privacy Policy
All the code from this post is available on Github.