CHAPTER 4
User Iinterfaces are defined as components in React. The term component is used by many other frameworks. We can also write web components natively using HTML5 features like custom elements and HTML imports.
Components have many advantages, and whether we are working with them natively using HTML5, or using a library like React, we get the following great benefits.:
Consider this UI:
Code Listing 11: HTML-based UI
<a href="”http://facebook.com"”> <img src="”facebook.png"” /> </a> |
What does this UI represent? If you speak HTML, you can parse it quickly here and say, “it'’s a clickable image.” If we'’re to convert this UI into a component, maybe ClickableImage is a good name for it.
Code Listing 12: Component-based UI
<ClickableImage /> |
When things get more complex, this parsing of HTML becomes harder, so components allow us to know quickly what the UI represents using the language that we'’re comfortable with (English in this case).
Here'’s a bigger example:
Code Listing 13: Component-based UI
<TweetBox> <TextAreaWithLimit limit={140} /> <RemainingCharacters /> <TweetButton /> </TweetBox> |
Without looking at the actual HTML code, we know exactly what this UI represents. Furthermore, if we need to modify the output of the remaining characters section, we know exactly where to go.
Think of components as functions. This analogy is actually very close to the truth, especially in React. Functions take input, they do something (possibly on that input), and then give us back an output.
In functional programming, we also have pure functions, which are basically protected against any outside state; if we give them the same input, we'll always get the same output.
In React, a component is modeled after a function. Every component has private properties, which act like the input to that function, and we have a Vvirtual DOM output. If the component does not depend on anything outside of its definition (for example, if it does not use a global variable), then we label that component pure as well.
All React components can be reused in the same application and across multiple applications. Pure components, however, have a better chance at being reused without any problems.
For an example, let's implement our ClickableImage component.:
Code Listing 14: ClickableImage render function
var ClickableImage = function(props) { return ( <a href={props.href}> <img src={props.src} /> </a> ); }; ReactDOM.render( <ClickableImage href="http://google.com" src="http://goo.gl/QlB7wl" />, document.getElementById("react") ); |
Having variables for both the href and the src properties is what makes this component reusable.
Note how we defined the component as an actual function. We can create React components in multiple ways;. tThe simplest way is to use a normal JavaScript function that receives the component's props as an argument.
The function’s output is the virtual HTML view, which this component represents.
Don't worry about the syntax now—just focus on the concepts. To reuse this component, we could do something like:
We rendered ClickableImage with a Google logo:
props = { href: "http://google.com", src: "google.png" } |
We can simply reuse the same component with different props:
props = { href: "http://bing.com", src: "bing.png" } |
The src properties should be replaced with actual images, as we did in the render function of the previous example.
We create components to represent views. For ReactDOM, the React components we define will represent HTML DOM nodes.
The ClickableImage component in the last example was composed of two HTML elements. We can think of HTML elements as built-in components in the browser. We can also use our own custom components to compose bigger ones. For example, let's write a component that displays a list of search engines.:
Code Listing 15: SearchEngines Mockup
var SearchEngines = function(props) { return ( <div className="search-engines"> <ClickableImage href="http://google.com" src="google.png" /> <ClickableImage href="http://bing.com" src="bing.png" /> </div> ); } |
If, for example, we have the data in this format:
Code Listing 16: Search Engines Data
var data = [ { href: "http://google.com", src: "google.png" }, { href: "http://bing.com", src: "bing.png" }, { href: "http://yahoo.com", src: "yahoo.png" } ]; |
Then, to make <SearchEngines data={data} /> work, we just map the data array from a list of objects to a list of ClickableImage components:
Code Listing 17: SearchEngines render function
var SearchEngines = function(props) { return ( <List> {props.data.map(engine => <ClickableImage {...engine} />)} </List> ); }; ... ReactDOM.render( <SearchEngines data={data} />, document.getElementById("react") ); |
The three dots in ...engine means spread the attribute of an engine as flat properties for ClickableImage, which is equivalent to doing:
href={engine.href} src={engine.src} |
This SearchEngines component is now reusable. It can work with any list of search engines we give to it. We also used the ClickableImage component to compose the SearchEngines component.
Everything changes eventually. In React, a component manages its changes using a state object. In addition to the data we pass to components as props, React components can also have a private state, which can change over time.
For example, a timer component can store its current timer value in its state.
Code Listing 18: Timer component
<Timer initialSeconds={42} /> |
This timer will start at 42 seconds and count down to 0, decrementing its state every second. At second 0, its private state will be 42; at second 1, the private state will be 41; and at second 42, the private state will be 0.
With React, we just make the timer component display its private state.:
Code Listing 19: Timer render function
function() { return ( <div> {this.state.counter} </div> ) } |
Every second, we decrement the counter state.:
Code Listing 20: Changing the State (Pseudocode)
Every second: state.counter = state.counter - 1 if state.counter reaches 0 stop the timer |
Here's the great news: React components recognize the private state changes. When changes happen, React automatically hits the Refresh button for us and re-renders the UI of a component. This is where React gets its name—it will react to the state changes, and reflect them in the UI.
It's time to officially learn how to create React components.
Let’s define our ClickableImage component, which understands two input properties, href and& src. Once we have this ClickableImage component ready, we can mount it in the browser using the ReactDOM render function.:
Code Listing 21: Rendering a React Component to the DOM
ReactDOM.render( <ClickableImage href="google.com", src="google.com" />, document.getElementById("react") ); |
Note: ReactDOM is a library that's maintained separately and can be used with React to work with a browser’s DOM. To be able to use it, you need to include its CDN entry or import it in your project. This JSBin template has a working exampleworking example of a component mounted with ReactDOM.
The first argument to the ReactDOM.render method is the React element that needs to be rendered, and the second argument is where to render that element in the browser. In this case, we’re rendering it to the HTML node with id="react".
There are three main ways to define a React component:
Since components are modeled after functions, we can use a vanilla JavaScript function to write pure components:
Code Listing 22: Stateless Function Component
var ClickableImage = function(props) { return ( <a href={props.href}> <img src={props.src} /> </a> ); }; |
When we use a function component, we’re not creating an instance from a component class; rather, the function itself represents what would be the render method in a regular component definition. If we design our application in a functional and declarative way, most of our components could just be simple stateless function components.
Stateless function components can’t have any internal state, they don’t expose any lifecycle methods, and we can’t attach any refs to them. If we need any of these features (which I will explain in later chapters), we will need a class-based component definition.
With the new ES2015 arrow function syntax, the ClickableImage component can be defined more concisely with:
Code Listing 23: Stateless Function Component with Arrow Function
var ClickableImage = props => ( <a href={props.href}> <img src={props.src} /> </a> ); |
React has an official API to define a stateful component. Our simple ClickableImage component would be:
Code Listing 24: React.createClass syntax
var ClickableImage = React.createClass({ render: function() { return ( <a href={this.props.href}> <img src={this.props.src} /> </a> ); } }); |
The createClass function takes a single argument, a JavaScript configuration object. That object requires one property, the render property, which is where we define the component’s function that describes its UI.
Note how with createClass, we don’t pass the props object to the render call. Instead, elements created from this component class can access their properties using this.props within the render function.
The this keyword references the instance of the component that we mounted in the DOM (using ReactDOM.render). Every time we mount a <ClickableImage /> element, we’re creating an instance of the ClickableImage component class.
In object-oriented programming terms, ClickableImage is the class, and <ClickableImage ClickableImage /> is the object instantiated from that class.
With createClass, we can use the private state of the component object, and we can invoke custom behavior in its lifecycle methods. To demonstrate both concepts, let's implement a Timer component.
First, here's how we’re going to use this Timer component:
Code Listing 25: Rendering the Timer Component
ReactDOM.render( <Timer initialSeconds={42} />, document.getElementById("react") ); |
The simple definition of this component, before considering the private state or the tick operation, is:
Code Listing 26: Timer Component render() Function
var Timer = React.createClass({ render: function() { return ( <div>{this.state.counter}</div> ); } }); |
The counter variable is part of the private state within a component instance. The private state object can be accessed using this.state.
Our Timer component has the property initialSeconds, which is where the counter should start. Properties of a component instance can be accessed using this.props, so if we need to read the value that we're passing to the initialSeconds property, we do use this.props.initialSeconds.
The state of a React component can be initialized using a getInitialState function in the createClass definition object. Anything we return from the getInitialState function will be used as the initial private state for a component instance.
We want the initial state for our counter variable to start as this.props.initialSeconds, so we do the following:
Code Listing 27: Timer Component Initial State
var Timer = React.createClass({ getInitialState: function() { return { counter: this.props.initialSeconds }; }, render: function() { return ( <div>{this.state.counter}</div> ); } }); |
Let's now define the “tick” operation. We can use a vanilla setInterval function to tick every second (1000 milliseconds). Inside the interval function, we need to change our component state and decrement the counter.
The ticking operation should start after the component gets rendered to the DOM so that we’re sure there is a div in the DOM whose content we can now control. For that, we need a lifecycle method.
Lifecycle methods act like hooks for us to define custom behavior at certain points in the lifecycle of a React component instance. The one we need here is componentDidMount(), and it allows us to define a custom behavior right after the component gets is mounted in the DOM.
Code Listing 28: Timer Interval In componentDidMount()
var Timer = React.createClass({ getInitialState: function() { return { counter: this.props.initialSeconds }; }, componentDidMount: function() { var component = this; setInterval(function() { component.setState({ counter: component.state.counter - 1 }); }, 1000); }, render: function() { return <div>{this.state.counter}</div>; } }); |
There are a couple of things to notice here:
This Timer component is ready, except that the timer will not stop, and it will keep going to the negative side. We can use the clearTimeout function to stop the timer. Go ahead and try to do that for our component, and come back to see the following full solution.:
Code Listing 29: Timer Component Full Definition
var Timer = React.createClass({ getInitialState: function() { return { counter: this.props.initialSeconds }; }, componentDidMount: function() { var component = this, currentCounter; component.timerId = setInterval(function() { currentCounter = component.state.counter; if (currentCounter === 1) { clearInterval(component.timerId); } component.setState({ counter: currentCounter - 1 }); }, 1000); }, render: function() { return <div>{this.state.counter}</div>; } }); ReactDOM.render( <Timer initialSeconds={42} />, document.getElementById("react") ); |
ES2015 was finalized in 2015, and with it, we can now use the class syntax. A class is syntax sugar for JavaScript’s constructor functions, and classes can inherit from each other using the extends keyword.:
Code Listing 30: ES2015 Class Syntax
class Student extends Person { } |
With this line, we define a new Student class that inherits from a Person class.
The React API has a class that we can extend to define a React component. Our ClickableImage definition becomes:
Code Listing 31: React.Component Syntax
class ClickableImage extends React.Component { render() { return ( <a href={this.props.href}> <img src={this.props.src} /> </a> ); } } |
Within the class definition, the render function is basically the same, except that we used a new ES2015 syntax to define it. The word function can be completely avoided in ES2015.
Let's look at our Timer example using the ES2015 syntax. Try to identify the differences.:
Code Listing 32: Timer Component Using Class Syntax
class Timer extends React.Component { constructor(props) { super(props); this.state = { counter: this.props.initialSeconds }; } componentDidMount() { let currentCounter; this.timerId = setInterval(() => { currentCounter = this.state.counter; if (currentCounter === 1) { clearInterval(this.timerId); } this.setState({ counter: currentCounter - 1 }); }, 1000); } render() { return ( <div>{this.state.counter}</div> ); } } |
Here are the differences explained:
Component classes, elements, and instances
Sometimes you’ll find these terms mixed up in guides and tutorials. It’s important to understand that we have three different things here: