Two-way Data Binding in ReactJS - Part II

In this post we explain how to use lodash to access nested object properties on a components state object so they can be bound to JSX form elements..

Abraham Serafino

Nested State

In Part I, we learned how to bind top-level state properties to form elements in JSX, so that updates to those elements automatically updated state and vice versa. However, the method we used breaks down when the structure of our state object is more complex; for example:

this.state = {
  isGoing: true,
  guest1: { name: 'Alice' },
  guest2: { name: 'Bob' },
};

Our method retrieves the value from state[key], and updates the value with setState({ [key]: /* … */ }), which means we can’t bind an element like this:

<input type="text" {...model('guest1.name')} />

… because this.state[‘guest1.name’] is undefined.

While it might make sense to “flatten” the structure of your state object in this specific case, you’re going to need support for nested objects at some point, especially if you want to work with REST data.

Fortunately, lodash can “drill down” into nested objects to find specific properties; so adding this capability to our binder method is as simple as calling lodash’ get() and set() methods:

import get from 'lodash.get';
import set from 'lodash.set';
import merge from 'lodash.merge';

function bindModel(context) {
  return function model(path) {
    const value = get(context.state, path, '');

    return {
      value,
      checked: value || false,

      onChange(event) {
        const originalValue = value;
        const target = event.target;
        const newValue =
          target.type === 'checkbox' ? target.checked : target.value;

        const newState = {};
        set(newState, path, newValue);

        // remember, we cannot call set() directly on the state object,
        // because mutating the state object has unexpected results
        context.setState(merge(context.state, newState));

        if (typeof context.handleChange === 'function') {
          context.handleChange(path, newValue, originalValue);
        }
      },
    };
  };
}

export default bindModel;

Now we can use dot (.)- delimited property names in our component JSX:

render() {
  const model = bindModel(this);

  return (
    <div>
      <form>
        <label>
          <input type='checkbox' {...model('isGoing')} />
          Attending&nbsp;
        </label>

        <p>
          <label>Guest 1:&nbsp;</label>
          <input type="text" {...model('guest1.name')} readOnly={!this.state.isGoing} />
          <br/>
        </p>

        <p>
          <label>Guest 2:&nbsp;</label>
          <input type="text" {...model('guest2.name')} readOnly={!this.state.isGoing} />
          <br/>
        </p>
      </form>

      <label>{this.summary()}</label>
    </div>
  );
}

To see the full example, browse this source at https://github.com/abraham-serafino/react-data-binding/tree/Part-II.

In Part III, we’ll round out the features of our new library by adding support for variable-length arrays.

Share this Post

Related Blog Posts

JavaScript

Two-way Data Binding in ReactJS - Part III

April 28th, 2017

In Part III of the series we explore how to tackle variable-length arrays, even if those arrays appear in nested objects within a components state.

Abraham Serafino
JavaScript

Two-way Data Binding in ReactJS - Part I

April 24th, 2017

Part I of a series on two-way data binding in ReactJS. Describes how to take advantage of JSX dynamic attributes to implement rudimentary data binding.

Abraham Serafino
JavaScript

Ahead of Time Compilation with Angular

April 14th, 2017

Pre-compiling your Angular code can significantly reduce bundle size and improve performance. In this article we will review an example app to see it in action.

Mike Plummer

About the author

Abraham Serafino

Sr. Consultant

Abraham is a full-stack web enthusiast with experience in a wide variety of frameworks and technologies. Over the course of his career, Abraham has found himself working at large and small companies, driving best practices and providing uniquely inventive software solutions. He jumps at any opportunity to apply his knowledge of Spring and AngularJS.