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.
By this time in the Ext JS to React series you may find yourself asking, “So, is now a good time to talk about form fields?” It sure is! Forms are a primary way for end users to supply feedback through our web applications, so they’re used a lot in the real world. Ext JS includes a number of form fields within the framework designed to make form layouts, validation, and submission easier. Before getting into handling the user input, let’s first take a look at how to create the form fields themselves using React.
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.
Input / Textfield
Ext JS provides a number of text input fields that can be fairly easily replicated in React using the various input types. A number of these are HTML 5 and supported on IE10+ but will require a different strategy if your supported browser base is lower than IE10. The input field type is created using the xtype of “textfield”. Other input types such as “password”, “url”, and “search” have their own xtypes / classes: passwordfield
(Ext.field.Password), urlfield
(Ext.field.Url), and searchfield
(Ext.field.Search). When defining forms in React, you can use the native HTML types:
import React, { Component } from 'react'; import './App.css'; class App extends Component { render() { return ( <form className="login-form"> <input type="text" /><br /> <input type="password" /><br /> <input type="url" /><br /> <input type="search" /><br /> </form> ); } } export default App;
Note: A number of the Ext JS input field config options have HTML attribute counterparts that can be used when defining input fields in React. One attribute of note is the value
. In plain HTML, <textarea>
and <select><option>
do not use value to set their initial value. However, React normalizes this for you, allowing all three input options to be initially set using a value=""
prop.
You can review the sample code on the Git repo.
Textarea
A textarea with 4 rows in React:
import React from 'react'; import './App.css'; export default () => <textarea rows="4"/>;
You can review the sample code on the Git repo.
Selectfield
The Ext JS combobox
(Ext.field.ComboBox) is somewhat of a mashup of the native <select>
and <input>
field. A combobox
configured with editable: false
is comparable to a <select>
field. In React, you can return a select field with option elements for each eligible choice from the select component’s options
array:
import React, { Component } from 'react'; import './App.css'; class App extends Component { state = { value: '_default' }; options = ['Solids', 'Stripes'] render() { return ( <select value={this.state.value} onChange={e => this.handleSelectChange(e)} > {[ <option disabled key="_default" value="_default"> select one... </option>, ...this.options.map( item => ( <option key={item} value={item.toLowerCase()}>{item}</option> ) ) ]} </select> ); } handleSelectChange (e) { this.setState({ value: e.target.value }); } } export default App;
The above example introduces a couple of nuances. The onChange
event handler serves a couple of purposes. First, without an onChange
handler, when you define a field with a value=
, the select field will be rendered as readonly
. Second, the change handler sets the value of the select field via the React component’s internal state by having the value derived from the state initially.
<select value={this.state.value} //...
This circular setup is what is known as a controlled component. It is the recommended path, as the value of the field is then settable by the user or any mechanism that has access to the component state. For information on “uncontrolled” components see React’s documentation.
You can review the sample code on the Git repo.
Combobox
To present a list of possible choices, but also allow any user input provided, you can omit the editable
config option in the Ext JS example. To provide the same suggestion list, but still allow user input, have your React component return an <input>
field using the list attribute along with a datalist
(IE10+ and unsupported in Safari). This provides the added benefit of datalist filtering as the user types:
import React, { Component } from 'react'; import './App.css'; class App extends Component { state = { color: '' }; options = ['Red', 'Blonde', 'Brown', 'Black'] render() { return ( <div> <input value={this.state.color} list="hair-color" onChange={e => this.handleChange(e)} /> <datalist id="hair-color"> {[this.options.map( item => <option key={item} value={item} /> )]} </datalist> </div> ); } handleChange(e) { this.setState({ color: e.target.value }); } } export default App;
You can review the sample code on the Git repo.
Range / Slider
The slider
(Ext.slider.Single) field is comparable to the range field (IE10+) we’ll return from the React example. Let’s initialize the field with a value of 50, a minimum value of 0, a maximum value of 100, and an increment / step of 10.
import React, { Component } from 'react'; import './App.css'; class App extends Component { state = { value: 50 }; render() { return ( <input onChange={e => this.handleChange(e)} type="range" value={this.state.value} min="0" max="100" step="10" /> ); } handleChange (e) { this.setState({ value: e.target.value }); } } export default App;
You can review the sample code on the Git repo.
Datefield
The datefield
(Ext.field.Date) displays a calendar for easy selection of a day, month, and year. Displaying a calendar view for easy date selection is a bit less trivial in React than in Ext JS. You can use the input field of type date
, but the implementation across browsers / platforms is incomplete. An example of the date input field in React is:
// npm install --save moment import React, { Component } from 'react'; import moment from 'moment'; class App extends Component { state = { value: moment().format('YYYY-MM-DD') }; render() { return <input type="date" value={this.state.value} onChange={e => this.handleChange(e)} />; } handleChange (e) { this.setState({ value: e.target.value }); } } export default App;
The date
type is not supported on IE or Safari. For additional cross-browser implementations, you may want to look at using a date field package like react-datepicker or react-dates Alternatively, you may want to consider a component library like kendo UI.
You can review the sample code on the Git repo.
Number / Spinner
In Ext JS there are two fields related to the input of numbers. The numberfield
(Ext.field.Number) restricts the input to numeric entries only, while the spinnerfield
(Ext.field.Spinner) adds up / down arrows in the field to enable users to increment and decrement the field value without explicitly entering a numeric value. An equivalent implementation of the spinner field (IE10+) in React would be:
import React, { Component } from 'react'; class App extends Component { state = { value: 50 }; render() { return <input type="number" value={this.state.value} onChange={e => this.handleChange(e)} step="10" min="0" max="100" />; } handleChange (e) { this.setState({ value: e.target.value }); } } export default App;
You can review the sample code on the Git repo.
Radio
Radio
fields (Ext.field.Radio) are defined with a shared name
config with each field instance possessing a distinct value
to pass on form submission. These are the same conventions used in HTML, as seen in the following React form with ‘red’, ‘blue’, and ‘green’ radio fields:
import React, { Component } from 'react'; class Radio extends Component { state = { checked: !!this.props.checked } render () { return <input type="radio" name={this.props.name} value={this.props.value} checked={this.state.checked} onChange={e => this.handleFieldChange(e)} /> } handleFieldChange (e) { this.setState({ checked: e.target.checked }); } } export default Radio;
Our App class can now use the Radio class directly within a form:
import React, { Component } from 'react'; import Radio from './Radio'; class App extends Component { render () { const colorRadioName = 'color'; return ( <form> <label> Red <Radio name={colorRadioName} value="red" checked /> </label> <br /> <label> Blue <Radio name={colorRadioName} value="blue" /> </label> <br /> <label> Green <Radio name={colorRadioName} value="green" /> </label> </form> ); } } export default App;
Let’s talk a little about some of the other differences in this example. First, we’ve made a couple of allowances for the sake of brevity. We’ve left out the styling of the labels and inputs, and while normally each class would be defined in separate files, we’ve added both the radio component and the form to the same file. The radio component has been defined on its own since the type, change handler, and checked property will be the same across each radio instance. We then pass in the desired name, value, and checked value. The checked value passed (if at all) shows up on the radio component’s props
object to then be set on the radio instance.
You can review the sample code on the Git repo.
Checkbox
Ext JS checkbox
(Ext.field.Checkbox) fields are defined similar to radio fields, but have unique `name` configs. The React checkbox example is also very similar to our radiofield example where the name fields are unique per field:
import React, { Component } from 'react'; class Checkbox extends Component { state = { checked: !!this.props.checked } render () { return <input type="checkbox" name={this.props.name} value={this.props.value} checked={this.state.checked} onChange={e => this.handleFieldChange(e)} /> } handleFieldChange (e) { this.setState({ checked: e.target.checked }); } } export default Checkbox;
Our App class can now use the Checkbox class:
import React, { Component } from 'react'; import Checkbox from './Checkbox'; class App extends Component { render () { return ( <form> <label> Music <Checkbox name="music" value="music" checked /> </label> <br /> <label> Dance <Checkbox name="dance" value="dance" /> </label> </form> ); } } export default App;
You can review the sample code on the Git repo.
Toggle
The Ext JS togglefield
(Ext.field.Toggle) is a stylized slider field that accepts only two values. The control has a slider element that is moved left to right when clicked / tapped. Using an existing example for reference, we’ll construct our toggle field’s HTML structure using a checkbox and its wrapping label for the form element. We’ll include a span that will operate as our visual control. First, we’ll define the toggle component:
import React, { Component } from 'react'; import './Toggle.css'; class Toggle extends Component { state = { checked: !!this.props.checked } render () { return ( <label className="toggle-wrap"> <div>{this.props.label}</div> <input type="checkbox" checked={this.state.checked} onChange={this.handleToggleChange} /> <div className="toggle-el"></div> </label> ); } handleToggleChange = (e) => { this.setState({ checked: e.target.checked }); } } export default Toggle;
Toggle CSS
With our React component created, we need to add the CSS that styles the toggle field’s UI. In this case, we’ll add it to an {appRoot}/src/Toggle.css
file to be imported by the Toggle class.
.toggle-wrap { position: relative; display: inline-block; width: 60px; height: 34px; } .toggle-wrap input {display:none;} .toggle-el { position: relative; cursor: pointer; border-radius: 34px; height: 100%; width: 100%; background-color: #ccc; -webkit-transition: .4s; transition: .4s; } .toggle-el:before { content: ""; position: absolute; border-radius: 50%; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; -webkit-transition: .25s; transition: .25s; } input:checked + .toggle-el { background-color: #f32195; box-shadow: inset 0 -20px 37px 0px rgba(0, 0, 0, 0.36); } input:focus + .toggle-el { box-shadow: 0 0 1px #f32195; } input:checked + .toggle-el:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); }
Our Toggle field can now be composed in our App:
import React, { Component } from 'react'; import Toggle from './Toggle'; class App extends Component { render () { return ( <Toggle /> ); } } export default App;
This results in a checkbox field (that is hidden, but being toggled via its wrapping <label>
) styled as a toggle control.
You can review the sample code on the Git repo.
Wrap Up
In the above field examples we created a loop that populated the field value with either passed-in props or the value manually entered within the field. Often times, the form fields are wrapped by a <form>
or some other container that can manage the field values at the container level. Hoisting the value-controlling code above the fields themselves sheds duplicate code, making maintenance of the “dumb” components a bit easier. With the overview of field pieces and parts complete, we’ll focus our attention in the next article on user input moderation using field validations.
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…