Ext JS to React: Binding with React

   JavaScript

This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.

With Ext JS 5, the concept of view models was introduced into the application architecture. The data used by an application’s components could be sourced from data housed in the view model. Conversely, updates in the UI could send data back up to the view model in a bidirectional relationship. Data in React, however, flows in only one direction. Data is supplied to components via props passed in from a parent component or through a binding with an application state manager like Redux.

Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.

Ext JS Simple Binding Example

Let’s look at a simple example using an HTML form and several fields. With Ext JS you have to define a view model and then configure bindings for the individual fields:

Ext.define('MyApp.view.User', {
    extend : 'Ext.form.Panel',
    xtype  : 'myapp-user',

    controller : {
        xclass : 'MyApp.view.UserController'
    },

    viewModel : {
        xclass : 'MyApp.view.UserModel'
    },

    defaultType : 'textfield',

    items : [
        {
            xtype      : 'displayfield',
            fieldLabel : 'User ID',
            name       : 'id',
            bind       : '{user.id}'
        },
        {
            fieldLabel : 'Name',
            name       : 'name',
            bind       : '{user.name}'
        },
        {
            fieldLabel : 'Email',
            name       : 'email',
            bind       : '{user.email}'
        },
        {
            fieldLabel : 'Phone Number',
            name       : 'phone',
            bind       : '{user.phone}'
        },
        {
            fieldLabel : 'Company',
            name       : 'company',
            bind       : '{user.company}'
        },
        {
            fieldLabel : 'Department',
            name       : 'department',
            bind       : '{user.department}'
        },
        {
            fieldLabel : 'Title',
            name       : 'title',
            bind       : '{user.title}'
        }
    ],

    buttons: [
        {
            text    : 'Submit', 
            handler : 'submit' // in the View Controller
        }
    ]
});

Given this Ext JS form, a common source for the data would be from an ajax call loading a store. The store could additionally be used to populate a grid. The grid’s selection model would be tied to the view model. Meaning that by selecting a row in the grid, the “user” record data would automatically be updated and then displayed in the form.

React Form Class

With React, there is no need for a dedicated view model class to hold the data. If you provide the data to a parent container, it can pass the appropriate data to the child fields. Child fields can then relay any changes to the parent container. The binding in the following React example form uses the properties on the fields:

import React, { Component } from 'react';

class Form extends Component {
  state = {
    user: Object.assign({}, this.props.user)
  };

  componentWillReceiveProps({ user }) {
    this.setState({
      user: Object.assign({}, user)
    });
  }

  render() {
    const { renderField, state, submit } = this;

    return (
      <form>
        {renderField(state, 'name')}
        {renderField(state, 'email', undefined, 'email')}
        {renderField(state, 'phone', 'Phone Number', 'tel')}
        {renderField(state, 'company')}
        {renderField(state, 'department')}
        {renderField(state, 'title')}

        <button onClick={submit}>Submit</button>
      </form>
    );
  }

  renderField = (state, name, label = name, type = 'text') => {
    return (
      <div style={{ marginBottom: '12px' }}>
        <label style={{textTransform: 'capitalize'}}>
          {label}
          <input
            type="text"
            name={name}
            value={state.user[name] || ''}
            onChange={this.handleFieldChange}
            style={{ marginLeft: '12px' }}
          />
        </label>
      </div>
    );
  };

  handleFieldChange = e => {
    const { name, value } = e.target;
    const user = Object.assign({}, this.state.user, { [name]: value });

    this.setState({ user });
  };

  submit = e => {
    e.preventDefault();

    // do submit
  };
}

export default Form;

We can use the form class in our starter app with the following:

import React from 'react';
import Form from './Form';

const App = () => <Form/>;

export default App;

 

Ext JS to React: Binding with React

 

React Form Class Explained

This may seem familiar from code you’ve seen in the form article. What’s going on here is the form is taking a user prop (from Form user={user} when instantiated) and setting values from that object onto each child input using the renderField method. The part here that is binding is the value prop on the input field. When data is passed as a prop to the form, changes to the value for each bound form field will result in that field being re-rendered.

As you type in a field, there is an onChange listener that updates the state. This updating will re-trigger a render process so that React can determine whether there was a DOM update that needs to happen. Once the state changes, that value is passed down to the fields to be updated since their input text is derived from the parent form’s state. In this example, we also used the componentWillReceiveProps lifecycle method. This is done so that if the parent’s user prop is ever updated the fields’ values remain in sync.

You can review the sample code on the Git repo.

Communicating Changes To a Parent Component

In this last example, something passed the user data to the form and the form managed its internal state based on any of the child items being changed. What if you don’t want to submit a form? What if instead, you want to notify the something passing in the data that a change was made? For example, say you have a grid and when you click on a row it shows a form to edit that row’s data. As you type you want the grid row to reflect the changes live. Ext JS allows for two-way binding which would support this, but React says data flows one way and that’s down. So there is no way to have React automatically update the parent data from one of the form fields.

In the last example, we used Object.assign to clone the user object so that we didn’t accidentally edit data. Since we nested the data within the user prop, that object is not handled by React. The object that was passed to the Form class is the same that is applied to the state if we didn’t use Object.assign to create a new object. Even though you technically can get two-way binding here, React’s convention only supports one-way, downstream binding. Additionally, React will not re-render if you simply update a property of a nested object.

Calling a Parent Method Example

In order to notify the entity that instantiated the form, it’s common to pass down a function that the form can execute:

import React from 'react';
import Form from './Form';

const App = () => (
  <Form
    onChange={change => {
      console.log('change', change);
    }}
  />
);

export default App;

Now within that onChange function that was passed, we can execute the following within the handleFieldChange method:

handleFieldChange = e => {
  const { onChange } = this.props;
  const { name, value } = e.target;
  const change = { [name]: value };
  const user = Object.assign(this.state.user, change);

  if (onChange) {
    onChange(change);
  }

  this.setState({ user });
};

With this handler in place we only pass what changed. That allows us to patch the full user object we passed to the form and perform any action needed. As a user types in one of the fields, the onChange function is executed. This allows us to handle the change such as updating the grid row data on the fly. In doing so, we’ve created a two-way binding relationship without breaking React conventions! This can be a great way to perform real-time checks such as field validations. For example, an asynchronous check can be done on the server to ensure a username entered is not only valid, but currently available.

You can review the sample code on the Git repo.

Wrap Up

Since Ext JS is more than just a view library like React, it has mechanisms to handle two-way binding. React only wants to use one-way binding exclusively. However, as we’ve explored in the examples above, there are still ways to notify parent components of the changes announced from child items. Fortunately, the feedback loop between components is easy to set up and only needs to be created as the situation dictates leaving your code base as lean as possible! Stay tuned for the coming blogs where we continue to look at how data is managed within your application using the popular state manager, Redux.


Like What You See?

Got any questions?