PMG Digital Made for Humans

ELEGANT TWO WAY DATA BINDING WITH REACT MIXINS

4 MINUTE READ | November 23, 2015

ELEGANT TWO WAY DATA BINDING WITH REACT MIXINS

Author's headshot

Christopher Davis

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

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>    }});
Now we can reuse this simple input to state linking across objects.

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

Bringing news to you

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.


Related Content

thumbnail image

AlliPMG CultureCampaigns & Client WorkCompany NewsDigital MarketingData & Technology

PMG Innovation Challenge Inspires New Alli Technology Solutions

4 MINUTES READ | November 2, 2021

thumbnail image

Applying Function Options to Domain Entities in Go

11 MINUTES READ | October 21, 2019

thumbnail image

My Experience Teaching Through Jupyter Notebooks

4 MINUTES READ | September 21, 2019

thumbnail image

Working with an Automation Mindset

5 MINUTES READ | August 22, 2019

thumbnail image

3 Tips for Showing Value in the Tech You Build

5 MINUTES READ | April 24, 2019

thumbnail image

Testing React

13 MINUTES READ | March 12, 2019

thumbnail image

A Beginner’s Experience with Terraform

4 MINUTES READ | December 20, 2018

ALL POSTS