Welcome back, dear reader, to yet another scintillating edition of Bōwst about React! In our previous post, we put together a robust build system using webpack and the Babel Javascript compiler. We used this system to compile our app and greet the earth with a hard won “HELLO WORLD”. If you missed the awesomeness of getting a build system up and running, definitely check it out here before continuing with this segment.
All caught up? Great! In this post, we’ll be getting into the good stuff – how to make our “app” into an app. Instead of just rendering static markup like it’s 1995, we’re going to start leveraging the true strength of React and building highly interactive pages worthy of the modern web. Since I’m super forgetful, we’re going to be building a note taking app to help me remember my lunch when I leave for work (which is obviously a high priority).
Our First Interactive Component
Let’s start with the most basic part of our app, actually typing out our notes. React highly encourages the encapsulation of discrete functionality into different components, which is part of why we’ve found it such a great fit for enterprise apps where new feature development and design adjustments are commonplace. By logically separating the app into a set of components, projects scale elegantly without unnecessarily complicating architecture and directory structure. While our note taking app probably won’t qualify as “enterprise grade”, we’re going to move forward with best practices anyways. Even the smallest projects have a way of getting more complicated, and we want to lay a solid foundation in case we suddenly end up with millions in VC funding to build our app (stranger things have happened!).
Right, so with all of that said, let’s get back into the code. We’ll start by creating a new component called “EditNote” which will contain the textbox to actual type out our note. We’ll start by importing our dependencies, which in this case is just React itself.
//this is ES6 syntax, it's essentially equivalent to: var React = require('react') import React from 'react';
Next, we’ll declare our component as a Class (which is ES6 syntax) and add a render method to output the markup.
//This is also ES6 syntax, it's similar to module.exports = React.createClass({}); //whatever is `exported` from this file will be available for other files to import (like we did with react above) export default class EditNote extends React.Component{ render(){ return ( <div> <textarea /> </div> ); } }
While there’s not a lot of code here, there’s a lot going on. Let’s break it down. The first keyword in the first line (“export”) indicates that we want our class to available when imported or required by other files. The default keyword indicates that, since multiple items can be exported from a single file, our class should be the default. The “class” keyword followed by the name of the component declares our class. Finally, “extends React.Component” indicates that our class should inherit the React.Component class. If you’re unfamiliar with class inheritance, it’s a common part of object-oriented programming. You can read more about it here.
Whew, and that was just the first line. Luckily it gets easier from here! We declare a method on the class which is reponsible for returning the final visual representation of the class. This method must be called “render” and doesn’t take any arguments. As discussed in the previous post, we use JSX for this instead of pure Javascript, which uses an XML-like syntax which is similar to HTML. As you can see, our component simply renders a div with a textarea inside of it.
Now we want to include this component in our app and render it on the page so we can see it! Let’s go back to our main app component (app.jsx) and import this component into that file so we can use it. This import statement will depend on the path of your new component relative to the app.jsx file, but should look something like this:
import EditNote from "./../components/EditNote.jsx";
Great, now we’ve got that class available to us in our app.jsx file. Let’s adjust app.jsx to render an instance of our EditNote component. I changed the render function of app.jsx to the following:
render(){ return ( <div> {/* Since JSX is basically XML, all components can only consist of a single node. That's why I've wrapped everything in a div */} <h1>Bowst About React - Notetaking App</h1> <EditNote /> </div> ); }
Notice how we’re rending an instance of our EditNote component using JSX syntax. If you’ve worked with Angular directives, this idea should be pretty familiar. In fact, Angular directives are kind of like the older cousin of React components and are their closest conceptual analogy (at least that I’m familiar with).
To see this on our page, just re-run the build script (npm run dev) and refresh our page. Wowzers! There’s our text box – exciting stuff!
Tracking Component State
The next step is to start tracking the value of the textarea, and we’re going to do so by introducing a new React concept called “state”. Almost all React components have this property, which is an object containing any information related to the state of the component. For example, in an accordian control, it might be an object with a boolean property called ‘isOpen’ which keeps track of whether the accordian is open or collapsed. We can use events (such as clicks) to change the state, which in turn causes the component to re-run the render function and update the DOM with any changes. This unidirectional event chain is what the React developers call a “one-way” data flow. Here at Bowst we really like this because it makes unwinding UI logic a breeze.
But I digress — back to our EditNote component. We’re going to use state to keep track of the value of the textarea so we can add it to our list of notes later. The first step in doing that is initializing the component’s state. We do this in a special constructor function in our EditNote class which run when we create an instance of our component. Take a look at the code below:
//ES6 makes JS class more inline with real object-oriented programming //this constructor method is called when an instance object is initialized //we'll cover the 'props' argument later constructor(props) { //we are extending or 'inheriting' another class here //we call the special 'super' method, which will call the constructor in the class we are inheriting from //in this case, it will call the constructor on the React Component object super(props); //Set the initial state of this component //the note property will hold the text of our note this.state = { note: "" }; }
The next step is to make sure our textarea component always represents the “note” property we’re storing in the component’s state. This is called a “controlled” element, which essentially means that the inputs value is represented in our component state.
<textarea value={this.state.note} onChange={this._handleChange.bind(this)} />
You can see above, we’re just setting the value of the textarea to the value we’re storing in our state. A few quick notes about this code:
- The “this” refers is a special keywork in javascript which by default inside a function represents the object on which the function was defined. Since our JSX syntax is inside the “render” function, this presents the instance of our EditNote component (since that’s where the render function is defined).
- When passing attributes (or props as they’re called in React) to a JSX element, you can use quotes (like you would in HTML) for static strings but must use the curly braces ({}) to pass a Javascript expression.
- We bind the change handler to ensure that the “this” keyword in our _handleChange method represents the instance of our EditNote component.
- The underscore function naming convention is totally not mandatory – we tend to do it to help us distiguish lifecycle and API functions (such as render) from our custom event handlers.
The other part of this you probably noticed is the onChange prop…what is that all about? I’m glad you asked! This is an event handler and can be passed a function which will be executed everytime the user changes the value in the textbox. We haven’t added that function to our EditNote component yet, let’s do that now – see the code below.
//this method updates the state every time the input is changed _handleChange(event){ this.setState({ note: event.target.value }); }
As you can see, this just uses the special “this.setState” method (provided to us since we’re inheriting React.Component) to update the “note” property of the state based on the event parameter. This parameter has a property called “target” (our textarea), which in turn has a “value” property (which represents the current value of our note). Since we update the state, React rerenders the component and updates the DOM as necessary. In this case, no DOM updates are neccessary since the value of the textarea control in the DOM matches the “value” prop on our textarea in the virtual DOM. There are tons of React events, and we can update the state of our component in response to any or all of them.
So now we’re tracking the value of our textarea in the component state. Let’s explore that a bit more and look at a click event in React. We’re going to add a button to our EditNote component that clears the textfield when clicked. Here’s the code for the button:
<button onClick={this._clearInput.bind(this)}> Clear Note </button>
And (you guessed it!) the code for the “_clearInput” function.
_clearInput(){ /* Since we're now controlling the value of the textfield with the state, clearing the textfield becomes trivial. We just set this.state.note to an empty string. */ this.setState({ note: "" }); }
All we’re doing here is setting the state with “note” as an empty string.This forces the component to re-render, and React handles updating the DOM with the new value of our textarea. Pretty neat, right! Okay, yes, clearing an input field isn’t exactly game changing functionality; however, the ability to specifically control any aspect of the component’s visual appeareance simply by modifying an object is pretty incredible. It makes a components UI a predictable state machine, which results in rapid development times and far fewer complex UI bugs to sort out. When issues do crop up, the one-way data flow architecture makes it super easy to follow the call stack logic (which certainly cannot be said for many other popular JS libraries – I’m looking at you jQuery).
The Whole Enchilada
That’s all we have time for this week. But before we go, let’s take a look at what everything looks like when put together:
//this is ES6 syntax, it's essentially equivalent to: var React = require('react') import React from 'react'; //This is also ES6 syntax, it's similar to module.exports = React.createClass({}); //whatever is `exported` from this file will be available for other files to import (like we did with react above) export default class EditNote extends React.Component{ //ES6 makes JS class more inline with real object-oriented programming //this constructor method is called when an instance object is initialized //we'll cover the 'props' argument later constructor(props) { //we are extending or 'inheriting' another class here //we call the special 'super' method, which will call the constructor in the class we are inheriting from //in this case, it will call the constructor on the React Component object super(props); //Set the initial state of this component //the note property will hold the text of our note this.state = { note: "" }; } //this method (explained below) updates the state everytime the input is changed _handleChange(event){ this.setState({ note: event.target.value }); } _clearInput(){ /* Since we're now controlling the value of the textfield with the state, clearing the textfield becomes trivial. We just set this.state.note to an empty string. */ this.setState({ note: "" }); } render(){ return ( <div> {/* This is what is called a 'controlled' form input in React. Here's how it works: 1) When the component is mounted, the value of the text area is set to this.state.note Recall that in the constructor() method, we just set this.state.note to an empty string 2) Each time the user enters input, it runs the method passed as the 'onChange' property and passes a synthetic DOM event (https://facebook.github.io/react/docs/events.html) 3) The change handler updates the state, setting this.state.note to value the user just entered 4) The component is re-rendered by exectuting the render function and updating the DOM with the new value */} <textarea value={this.state.note} onChange={this._handleChange.bind(this)} /> {/* Notice how we pass bound methods for the 'onClick' and 'onChange' properties. We bind them to the 'this' keyword so `this` in the method is the component. */} <button onClick={this._clearInput.bind(this)}> Clear Note </button> </div> ); } }
As you can see, this component cleanly encapulates a single part of our app’s functionality – the actual typing of a note. In the next edition of Bowst about React, we’re going to add some flair to our app and finish our EditNote component by adding the ability to add the value of the textarea our list of notes. Until next time!!