Introduction
I recently read a statistic that indicated that ReactJS has quickly become one of the mostly popular web front-end technologies. Therefore, as a software developer by trade, I thought it was time to learn it.
The first thing you will notice when learning ReactJS is that it is component-based; i.e., a ReactJS app, including the app itself, is made up of components. Therefore, I figured the most logical type of component to create with the new technology would be some type of UI component or widget. In the past, I have created UI widgets purely in JavaScript or TypeScript that mimic existing HTML elements but are easier to work with (particularly in JavaScript). So, I decided to create a “ListBox
” (or “DropDown
box”) based on the HTML select element.
The next thing you should understand about ReactJS is that, while not strictly necessary, it commonly takes advantage of a technology called JSX. JSX is an XML-like syntactic extension to JavaScript that allows you to embed UI markup inside JavaScript. The primary advantage of this is that it allows the UI markup and associated logic to be embedded in the same component which, as stated above, fits in nicely with the component-based model of ReastJS. While I won't go into detail about JSX since it is a separate technology, I have mentioned it here because it is used in the example below.
Another aspect of ReactJS development that should be noted is that it takes advantage of the latest JavaScript specification EMCAScript 6. Because of this and the integration with JSX, it commonly needs a transpiler like Babel to convert the application into plain EMCAScript 5 JavaScript code. I won't go into detail about configuring your development environment for ReactJS either.
Let's now jump into the code to get into the details of how ReactJS works and the benefits it provides in making front-end development easier.
Components
A component is commonly created by using the class syntax for EMCAScript 6 and extending (also known as 'inheriting' in object-oriented parlance) from the React.Component
class, as follows:
class App extends React.Component
{
}
Of course, we can name this class anything we want but by naming this particular class 'App
', it becomes rather obvious that this is our 'application' component representing the highest level component for our application. To “start” the application, you then just need to call the render
method on the ReactDOM
class and pass in your application component and the HTML element where it will render to, as follows:
ReactDOM.render(<App />, document.getElementById('content'));
All components within the application component will then be automatically rendered by calling the 'render
' method on each one; therefore, you are required to create a 'render
' method for each component your create. This 'render
' method is where you define the UI layout of your component and generally consists of standard HTML elements combined with JSX elements. Below is the render
method of our application class where the two ListBox
elements are JSX elements based on our new ListBox
component.
render()
{
return (
<div>
<h1>Car Selection</h1>
<ListBox Items={this.state.carMakes}
valuePropertyName='id'
selectedValue={this.state.selectedMake}
placeholder='Please select a make...'
textPropertyName='name'
OnSelect={this.OnCarMakeSelect} />
<ListBox Items={this.state.carModels}
selectedValue={this.state.selectedModel}
placeholder='Please select a model...'
OnSelect={this.OnCarModelSelect} />
</div>
);
}
In this example, the top-level
element for our application (ignoring the element where we are placing the application component on the page) is a 'div
' element that consists of an 'h1
' element plus two ListBox
components.
Properties
As you can see, we can define any number and type of attributes on a JSX element and assign values to them. These elements become “properties” on the corresponding child components. Note that in order to include JavaScript code in the JSX syntax, you must enclose it in curly brackets.
Now we will take a look at the ListBox
component definition with its render
method to see how the attributes defined in the component declaration are accessed.
class ListBox extends React.Component
{
render()
{
return (
<div className="listBox">
<select onChange={this.OnChange.bind(this)} ref='listBox'>
<option value="-1" disabled selected hidden>{this.props.placeholder}</option>
{this.props.Items.map((item, i) =>
<ListBoxItem Item={{ 'Text': item[this.textPropertyName],
'Value': item[this.valuePropertyName] }} />)}
</select>
</div>
);
}
}
You will immediately notice that the attributes assigned to the component are accessed using an object named 'props
' which is automatically created for you. As I mentioned in the beginning of this article, our listbox
is based off the 'select
' HTML element and this is where it is defined. Although it doesn't provide much of a benefit in our list box component, I have also created a ReactJS component for each 'option
' element, represented by the ListBoxItem
component, defined below:
class ListBoxItem extends React.Component
{
render()
{
return (
<option key={this.props.Item.Value}
value={this.props.Item.Value}>{this.props.Item.Text}</option>
);
}
}
We pass down two values (via attributes) to each ListBoxItem
component representing the value and text of each option HTML element. It is recommended that we add a 'key
' attribute whenever the component represents an item in a list as this attribute is used by ReactJS to identify which items have changed, have been added or have been removed.
It should be obvious by now that our list box component is rendered simply as a 'select
' HTML element.
Though it is not related to ReactJS specifically, it should be noted that the first option element we define in the listbox
is a way to provide placeholder text for the select HTML element since the select
element does not have a standard 'placeholder
' attribute. We also use it as a way to default the listbox
to have no option selected when it is first rendered.
It is also worth noting here that this ListBox
component provides a way to 'declare' the name of the properties on the objects in the Items list that represent the 'value
' and 'text
' properties of each option element. As you can see in the code below this is easily implemented using one of the ways in JavaScript to access fields on objects in an array by using brackets:
<ListBoxItem Item={{ 'Text': item[this.textPropertyName],
'Value': item[this.valuePropertyName] }} />
Now, let's get back to ReactJS.
State
One of the major benefits of ReactJS is how state is handled. Maintaining and manipulating state in any application can be cumbersome and make it difficult to debug. It is recommended that state be maintained at the highest level possible which is typically in the application component. When developing a component like our list box, it is tempting to put some type of state in the ListBox
component itself; for example, for keeping track of the currently selected item (or value) in the listbox
. However, in this case, the ReactJS way of doing this is to store the currently selected item in the application state.
State for each component should be stored in an object named 'state
' and state
is manipulated using a method named 'setState
' (which updates the state
object). Here is the state
as it is initially defined in our application component:
this.state =
{
carMakes: GetCarMakes(),
selectedMake: -1,
carModels: [],
selectedModel: -1
}
GetCarMakes()
is just a JavaScript function outside of our component that returns an array of car makes. carModels
is a state property initially defined as an empty array and will be manipulated each time a new make is selected.
selectedMake
and selectedModel
are the two properties used to store the currently selected make and model from the corresponding ListBox
components. It makes sense that these are tracked in the application component since, at some point, we will likely save these selections to some type of back-end store along with other data.
In ReactJS, it is recommended that state
be immutable where, for example, instead of modifying an array directly, the array is recreated each time it changes. This is done by calling the 'setState
' method. The nice thing about this method is that you only need to 'set
' the properties that are changing – any other existing state properties remain as part of the state
. As an example, have a look at the code for our OnCarMakeSelect
method of our application component:
OnCarMakeSelect(value, text)
{
this.setState({ selectedMake: value, carModels: GetCarModels(value), selectedModel: -1 });
}
We will discuss events and event handlers in a minute but just know that this method is called every time a new car make is selected. In that case, we update the currently selected make and the array of car models (as well as setting the currently selected model to -1
so no default option is selected in the models ListBox
and the placeholder is displayed).
Binding
This is the appropriate time to talk about binding in ReactJS. No JavaScript library or framework that interacts with UI would be complete with some type of binding. Unfortunately, while many of these libraries (e.g., KnockoutJS) provide two-way binding, ReactJS only provides one-way binding. The direction of this binding being from state to UI with no corresponding binding from UI back to state. In other words, you need to handle updating the state whenever something changes in the UI, which is generally done with DOM events combined with event handlers.
State
is bound to components declaratively where the component is declared. For example, as we saw above:
<ListBox Items={this.state.carMakes}
valuePropertyName='id'
selectedValue={this.state.selectedMake}
placeholder='Please select a make...'
textPropertyName='name'
OnSelect={this.OnCarMakeSelect} />
<ListBox Items={this.state.carModels}
selectedValue={this.state.selectedModel}
placeholder='Please select a model...'
OnSelect={this.OnCarModelSelect} />
this.state.carMakes
and this.state.selectedMake
are bound to the first ListBox
and this.state.carModels
and this.state.selectedModel
are bound to the second one.
As part of the binding process, one of the more powerful functions of ReactJS is its ability to determine, based on changes to state, which components need to be re-rendered (it is my understanding it is JSX that makes this easier). This makes ReactJS more efficient than other JavaScript libraries. This process is performed whenever the 'setState
' method is called. For example, in the OnCarMakeSelect
method shown above since the carModels
and selectedModel
state properties are 'bound' to the car models ListBox
component that component will automatically be updated (i.e., re-rendered).
Events and Event Handlers
As I mentioned above, DOM events and event handlers are required to handle binding from the UI back to your state. The preferred way to do this is by sending event handler functions to your child components as properties and update the state (and do whatever else) inside the component where you are storing the state. In our example, we have created two event handler functions in the application component as follows:
OnCarMakeSelect(value, text)
{
this.setState({ selectedMake: value, carModels: GetCarModels(value), selectedModel: -1 });
}
OnCarModelSelect(value, text)
{
this.setState({ selectedModel: value });
}
Notice we defined two parameters for each of these functions. We pass these functions to the ListBox
child components through a property that we named OnSelect
, as follows:
<ListBox Items={this.state.carMakes}
valuePropertyName='id'
selectedValue={this.state.selectedMake}
placeholder='Please select a make...'
textPropertyName='name'
OnSelect={this.OnCarMakeSelect} />
<ListBox Items={this.state.carModels}
selectedValue={this.state.selectedModel}
placeholder='Please select a model...'
OnSelect={this.OnCarModelSelect} />
In the ListBox
component, we have created a handler function that is called from the onChange
event from the select HTML element. This function is below:
OnChange(event)
{
this.props.OnSelect(event.target.value,
event.target.options[event.target.selectedIndex].text);
}
So, we take the data from the DOM event that is passed by default into this handler function and pass the selected value and associated text from the selected item in the select
element to the parent event handler assigned to the OnSelect
property. (This way, the parent component doesn't need to understand or worry about what a DOM event is.)
That's all there is to it except for one thing. You may have noticed the following code in the constructor method of the application component:
this.OnCarMakeSelect = this.OnCarMakeSelect.bind(this);
this.OnCarModelSelect = this.OnCarModelSelect.bind(this);
Since class methods are not bound by default in JavaScript, you must bind the this
object instance to each method that you pass to a component; otherwise, the this
property will be undefined when it is eventually called.
The final aspect of ReactJS that is part of this example and that I haven't discussed yet is the concept of the component life cycle. Each component has a life cycle whereby certain events can be handled at different stages of initializing and rendering and you can implement methods to inject your own logic at these different stages. I will leave it up to the reader to research the different events/methods, but I will explain the one we implement in our example. Below is the code for this event handler:
componentDidUpdate(nextProps, nextState)
{
var select = this.refs.listBox;
if (select)
{
select.value = this.props.selectedValue;
this.props.OnSelect(select.value);
}
}
The componentDidUpdate
is called just after every render. We implement this in our example because the car models list can change depending on the make selected and we want to reset the selected value (to -1
if we want no selected value when the list changes). Note the use of the 'refs
' property. This is a nifty ReactJS property that allows you to reference the DOM element for any of the DOM elements defined in the component. This is used in tandem with the 'ref
' attribute that can be defined on each DOM element.
Conclusion
I hope, by this rather small example, that I have hit upon the important concepts of ReactJS and that this will give you a major head start with ReactJS development.
History
- 01/25/2018 - Initial upload