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;
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.
Mitchell Simoens
Related Posts
-
Ext JS to React: Form Submission
This is part of the Ext JS to React blog series. You can review the…
-
Ext JS to React: Migration to Open Source
Worried about Migrating from Ext JS? Modus has the Answers Idera’s acquisition of Sencha has…