Introducing the Function Component
In this chapter, we will first start with a brief history of UI components developed over the past two decades and get to know how React uses UI components to put together an application. You will learn what a function component is, with an explanation of its props and the basic parent/child relationship. You will then get some tips on how to write a function component. At the end, you will see a practical function component example, Nav. The chapter also includes one bonus topic in the Appendix section: How many component types does React support?
We will cover the following topics in this chapter:
- History of UI components
- Building an app with components
- Introducing the function component
- Writing a function component
- Example of a function component
- Questions and answers
- Appendix
History of UI components
While we are fascinated by technology, it can also be intriguing to watch how slowly it evolves over time. In our case, it's HTML. On the surface, it doesn't appear to have changed for the past 20 years. You get that idea by comparing a typical web page written now with one written 20 years ago, and seeing that they look very similar, if not identical.
The following snippet shows what typical HTML page code looks like:
<HTML> <head> <meta charset="utf-8"> </head> <style> h1 { color: red; } </style> <script> console.log('start...') </script> <body> <h1>Hello World</h1> </body></HTML>
Those of us who have been in this industry long enough know that the web has been reshaped a couple of times. In particular, a tremendous amount of effort has been spent on how to generate the preceding HTML.
Web engineers have tried to divide the file up into multiple parts, including HTML, JavaScript, and CSS, and then put it back together upon rendering the file onscreen. They have also tried to load one or two parts on servers, and the rest on client computers. They have also tried various compilers or builders to autogenerate the file after each change to the source code. They have tried lots of things. Actually, almost anything you can think of regarding HTML has been tried a couple of times in the past, and people will not stop trying something just because someone else has tried it. In a sense, web technology gets re-invented every once in a while.
With so much new content being added to the web every day, engineers have found the HTML files a bit unmanageable. On the one hand, the demand is that users want to see more actionable items with quicker responses and, on the other hand, many of these actionable items on the screen create challenges for engineers to manage the workload and maintain the code base.
So, engineers are on a constant lookout for better ways to organize HTML files. If this organization is done right, it can help them not get overwhelmed by a plethora of elements on the screen. At the same time, organizing files well means a scalable project, since the team can divide the project into smaller pieces and work on each in a divide-and-conquer way.
Let's take a look at the history of how technologies using JavaScript assisted with these topics. We will choose four technologies for this conversation – jQuery, Angular, React, and LitElement.
jQuery
jQuery is a library used to manipulate the Document Object Model (DOM) elements on the screen. It recognizes the challenges of working with the DOM directly, thereby providing a utility layer to simplify the syntax of finding, selecting, and manipulating DOM elements. It was developed in 2006 and has been used by millions of websites since then.
What's great about jQuery is that it can work with an existing HTML by creating a wrapper around it using the famous $ symbol, as you can see in the following code:
$(document).ready(function(){ $("button").click(function(){ $(this).css("background-color", "yellow"); $("#div3").fadeIn(3000); $("#p1").css("color", "red") .slideUp(2000) .slideDown(2000); });});function appendText() { var txt1 = "<p>Text.</p>"; var txt2 = $("<p></p>").text("Text."); var txt3 = document.createElement("p"); txt3.innerHTML = "Text."; $("body").append(txt1, txt2, txt3);}
jQuery didn't have much competition when it came to changing color, font, or any attribute of an element at runtime. It made it possible to organize large chunks of business logic code into functions stored in multiple files. It also provided a modular way to create reusable UI widgets through one of its plugins at the time.
Complete separation between HTML and JavaScript was strongly favored back then. At that time, people believed that this way of doing things helped to raise productivity, since people who work with website styles and behaviors can be from two departments. Theming, the word describing the application of style to a site, was gaining popularity and some jobs were looking for developers who could make a site look as beautiful as a Photoshop design.
Angular
Angular is a web framework used to develop a Single-Page Application (SPA). It was invented by Google in 2010. It was quite revolutionary at the time, because you could build a frontend application with it. This means that the code written in Angular could take over the body of HTML and apply logic to all elements within it at runtime. All code was run at the browser level, resulting in the word "frontend" starting to appear in job résumés. Since then, web developers have been roughly categorized as "backend," "frontend," and "full stack" (which means both frontend and backend).
The code that Angular uses continues to be built on existing HTML by attaching additional tags to it like so:
<body> <div ng-app="myApp" ng-controller="myCtrl"> <p>Name: <input type="text" ng-model="name" /></p> </div> <script> var app = angular.module('myApp', []); app.controller('myCtrl', function($scope) { $scope.name= "John"; }); </script></body>
The controller and module introduced by Angular can imbue business logic to sections of HTML with unique scopes. Angular supports components and directives out of the box, which allows us to reference all relevant HTML and JavaScript together in a single file (although the HTML file still needs to be written in a separate file):
function HeroListController($scope, $element, $attrs) { var ctrl = this; ctrl.updateHero = function(hero, prop, value) { hero[prop] = value; }; ctrl.deleteHero = function(hero) { var idx = ctrl.list.indexOf(hero); if (idx >= 0) { ctrl.list.splice(idx, 1); } };}angular.module('heroApp').component('heroList', { templateUrl: 'heroList.html', controller: HeroListController});
The component created via Angular can be reused afterward in an HTML file.
React
React, also known as React.js, was developed by Facebook and released in 2013 as a JavaScript library for building UI components. Although it wasn't specifically marketed as a web framework, it has been used by developers to build single-page or mobile applications, and has been favored by start-up companies in particular ever since.
What was controversial at the time was how it treated HTML statements. Instead of leaving them in a HTML file, it actually asked to take them out and put under a render function of a component, like so:
<div id="root"></div><script type="text/babel"> class App extends React.Component { render() { return <h1>Hello World</h1> } } ReactDOM.render(App, document.getElementById('root'));</script>
This unique approach favors the component design much more than the integrity of the HTML file. This was (almost) the first time you could put HTML and JavaScript together under the same file. We call it HTML here because it looks like HTML, but actually React creates a wrapper to transform the HTML into JavaScript statements.
When React was introduced, it came with a class component and in 2015, it added support for a function component, so you can write the logic under a function instead of a class:
<script type="text/babel"> const App = function() { return <h1>Hello World</h1> }</script>
With React, the HTML files don't get touched as often as they used to be; in fact, they don't get changed at all, since the HTML content is all relocated to React components. This approach can still be controversial today because people who don't care about the location of the HTML would get onboard very easily, whereas people who care about the classical way of writing HTML would stay away. There's also a mentality shift here; with React, JavaScript becomes the focus of web development.
LitElement
Polymer was developed by Google and released in 2015, designed to build web applications using web components. In 2018, the Polymer team announced that any future development would be shifted to LitElement to create fast and lightweight web components:
@customElement('my-element')export class MyElement extends LitElement { ... render() { return html` <h1>Hello, ${this.name}!</h1> <button @click=${this._onClick}> Click Count: ${this.count} </button> <slot></slot> `; }}
There are quite a few similarities between React and LitElement since it allows you to define a class component with a render function. What's unique about LitElement is that once the element is registered, it can behave like a DOM element:
<body> <h1>Hello World</h1> <my-element name="abc"> <p> Let's get started. </p> </my-element></body>
There's no apparent entry point for integrating LitElement into HTML since it doesn't need to gain control of a body element before using it. We can design the element somewhere else, and when it comes to its use, it's more like using an h1 element. Therefore, it perfectly preserves the integrity of the HTML file while outsourcing the additional capability to the custom element, which can be designed by others.
The goal of LitElement is to have the web component work in any web page within any framework.
20 years ago, we didn't know what the web would become. From this brief historical review of jQuery, Angular, React, and LitElement, it's clear that an idea of having UI components has emerged. A component, like a block of LEGO, can do the following:
- Encapsulate functionalities inside
- Be reused in other places
- Not jeopardize the existing site
Thus, when we use the component, it takes the following syntax:
<component attr="Title">Hello World</component>
Essentially, this isn't too different from where we started with writing HTML:
<h1 title="Title">Hello World</h1>
There's a hidden requirement for a component here. While the components can be designed separately, in the end, they have to be put together to serve a higher purpose, to finish building a site. Therefore, as atomic as each component is, there still needs to be a communication layer to allow blocks to talk to one another.
As long as components are functioning and there is communication between them, the app can function as a whole. This is actually the assumption of a component design along with building a site.
So, into what category does our book fall? Our book is about building components under React, especially building smart components that can serve as a reusable block and are able to fit in an app. The technology we have chosen here is hooks inside a function component.
Before we get into the details of components and hooks, let's first take a brief look at how components can be put together to build an application.
Building an app with components
To start building an application, here's a block of HTML you can start with:
<!doctype HTML><HTML lang="en"> <body> <div id="root"></div> </body> </HTML>
These days, we have more and more SPAs that update parts of pages on the fly, which makes using the website feel like a native application. A quick response time is what we are aiming for. JavaScript is the language to deliver this goal, from displaying the user interface to running application logic and communicating with the web server.
To add logic, React takes over one section of the HTML to start a component:
<script> const App = () => { return <h1>Hello World.</h1> } const rootEl = document.getElementById("root") ReactDOM.render(<App />, rootEl)</script>
The render function in the preceding code, provided by ReactDOM, accepts two input arguments, which are a React element and a DOM element, rootEl. rootEl is where you want React to render, in our case, a DOM node tagged with the root ID. What React renders to rootEl can be found defined in a function component, App.
It's important to tell the difference between App and <App /> in React. App is a component, and there has to be a definition out there to describe what it can do:
const App = () => { return <h1>Hello World</h1> }
Whereas <App /> is one instance of the App component. A component can have lots of instances created from it, quite similar to the instance of a class in most programming languages. Creating an instance out of a component is the first step to reusability.
If we launch the preceding code in a browser, we should see it display the following Hello World title:
Figure 1.1 – Hello World
Playground – Hello World
Feel free to play with this example online at https://codepen.io/windmaomao/pen/ExvYPEX.
To have a fully functional application, normally we would need more than one page. Let's take a look at a second page.
Multiple pages
Building a "Hello World" component is the first step. But how does a single component like that support multiple pages so that we can navigate from one page to another?
Say we have two pages, both defined in components, Home and Product:
const Home = () => { return <h1>Home Page</h1>}const Product = () => { return <h1>Product Page</h1>
To display either Home or Product, we can create a helper component:
const Route = ({ home }) => { return home ? <Home /> : <Product />}
The preceding Route component is a bit different; it carries an input argument, home, from the function definition. home holds a Boolean value and based on it, the Route component can switch between displaying <Home /> or <Product />.
Now it's a matter of determining what the value for home in App is:
const App = () => { const home = true return <Route home={home} />}
In the preceding code, the App component is amended to include a home variable, which gets passed to the Route component.
You might have noticed that the current code will only display the Home page because we have set home to true. Don't worry. This whole book is about teaching you how to set the home value. For now, just imagine the value would be flipped from true to false based on a user mouse click, and for the time being, you can manually change the home value.
The App component can grow bigger as more and more components are added underneath it with this routing mechanism. This is partly why the first component in a React application is named App. Although you can name it whatever you want, just remember to use a capitalized letter for the first letter.
Playground – Home Page
Feel free to play with the example online at https://codepen.io/windmaomao/pen/porzgOy.
Now we can see how React puts together an app, so without further ado, let's get to the component in React.
There are mainly two component types that React supports – a class component and a function component. This book will focus on a function component. If you are interested in other component types, please check out the Appendix A – How many component types does React support? section at the end of this chapter.
Introducing the function component
"This pattern is designed to encourage the creation of these simple components that should comprise large portions of your apps." – Sophie Alpert
In this section, we are going to introduce you to the function component. When the function component was first introduced in React 0.14 in August 2015, it was named as a stateless pure function:
function StatelessComponent(props) { return <div>{props.name}</div>}
The main intention was that "stateless pure-function components give us more opportunity to make performance optimizations."
A function component with no state, by default, is designed to take the following function form:
Figure 1.2 – Function component definition
We are going to explore parts of a function component in detail in the next subsections.
Function props
The input argument of this function is referred to as a prop. Props take an object format under which we can define any property. Each property is referred to as a prop. For instance, Figure 1.2 defines a Title component with a text prop.
Because props are objects, there's no limitation to how many props can be defined under that object:
const Title = ({ name, onChange, on, url }) => {...}
The job of a prop, similar to an input argument, is to pass a value to the function. There is also no limitation in terms of the type of prop. Since each prop is a property of an object, it can be a string, a number, an object, a function, an array, or anything that can be assigned using a JavaScript expression, as in the following example:
const Title = ({ obj }) => { return <h1>{obj.text}</h1>}const Title = ({ fn }) => { return <h1>{fn()}</h1>}
In the preceding code, the first case passes an obj prop carrying a text property, while the second case passes an fn prop that gets invoked inside.
Once a function component has been defined, it can be used as many times as you want in other places via its instances:
const App = () => { return <Title text="Hello World" /> }
In the preceding code, a Title component instance is used in the definition of an App component.
And when the App component is updated, a string, "Hello World", is assigned to the text prop of the Title component. The usage of the Title component reminds us of the HTML statement, and the text prop reminds us of the attribute of the DOM element.
We have actually seen the usage of an App component instance as well at the beginning:
ReactDOM.render(<App />, rootEl)
In short, you can define a component, but to see what it displays on the screen, its instance needs to be used.
Children prop
All the props of a function component should be defined explicitly, just like input arguments. But, there's a prop worth knowing early on that isn't apparent to follow this rule. This is called a children prop:
const App = () => { return ( <Title> Hello World </Title> ) }
You might be using the preceding code without knowing how exactly the "Hello World" string is put under the Title component. Interestingly, the string is wired to the component via a children prop. This will become clear when we get to the definition of the Title component:
const Title = ({ children }) => { return <h1>{children}</h1>}
Essentially, the App component takes "Hello World" and assigns it to the children prop before invoking the Title component instance. You might wonder what happens if we forget to include the children prop when defining the Title component:
const Title = () => { return <h1>Haha, you got me</h1>}
In that case, "Hello World" is ignored and the App component reduces to the following case:
const App = () => { return <Title />}
Apparently, this is not intended since, if you put children elements under a component, then a children prop has to be defined explicitly in the function definition. This means that a children prop still needs to be explicitly written on the function interface.
In fact, the children prop is the reason why a component can be nested under another component. React uses this children mechanism to reproduce how the HTML writes in general.
Parent and child
In React, props are the mechanism for components talking to one another. We can generalize this idea by using two components normally involved in the communication to a parent and a child, as we have already seen in App and Title:
const Title = ({ text }) => { return <h1>{text}</h1>}const App = ({ flag }) => { const text = flag ? "Hello" : "World" return <Title text={text} />}
In the preceding example, a Title component accepts text as one of the props. An App component sends the "Hello" text to the Title component if the flag is true, otherwise, it sends the "World" text to Title.
Who sends the flag info to the App component? That will be the parent of App. This can easily be constructed to form a tree, where we have branches and sub-branches, and it reaches the leaves at the ends. Notice that this formation is done solely through the usage of props on each node (component).
Once a piece of info gets into a component, the prop binds its value to a local scope variable. From then on, it's the child's job to continue managing its local variable. It can be used pretty flexibly with one limitation. It's not expected to be changed! Or, if you ever change it, the change would not be reflected in the parent component. This behavior is the same as how we use a function with input arguments and its inner scope. The information passing is a one-way ticket.
So now comes a big question. What if we want to reflect the change to a parent component done by a child component? How can a one-way ticket get us the information back?
This is also done through a prop. As I mentioned, a prop can take any format, hence we can use a function prop:
const Child = ({ change }) => { const onChange = () => { change() } return <input onChange={onChange} />}const Parent = () => { const change = () => { console.log("child notify me") } return <Child change={change} />}
In the preceding code, we sent a function defined in Parent through a change prop. Inside the Child component, when a user starts to type in any character to an input box, it fires an onChange event where we can invoke the change function. Whenever this happens, you will see the child notify me message in the Parent component.
Essentially, this technique is what we refer to as a callback in JavaScript. The parent provides a mechanism to notify that something has changed using a callback function. Once the callback function is created, it can be sent to any child to gain the ability of notification to the parent.
In a typical parent/child relationship in React, it's recommended that a prop value should not be changed by the child. Instead, it should be done through a function prop. When comparing React to other libraries, a "one-way" ticket is what we use to refer to this behavior. In the React community, we rarely use this word because this is the behavior designed at its birth.
Now that we know the definition of a function component and the role props play in building a component, let's take a look at how, in general, we write a function component.
Writing a function component
The function, representing a component, defines what to update on the screen. It returns a value composed of some HTML -like code. You should be quite familiar with elements such as <ul> and <li>; React also allows the addition of JavaScript expressions under these elements. When used together, it requires the JavaScript expression to be wrapped in a pair of brackets, {}. The job of this expression is to provide dynamic HTML content.
For instance, if we have a text variable and would like to display it, we could do the following:
const Title = () => { const text = "Hello World1" return <h1>{text}</h1>}
Or, if the text is returned from a function, we can do the following:
const Title = () => { const fn = () => "Hello World" return <h1>{fn()}</h1>}
We know that this JavaScript expression is filled in the location where the children prop is.
The children element does not have to be a single element; it can be an array of elements as well:
const Title = () => { const arr = ['Apple', 'Orange'] return ( <ul> {arr.map((v) => ( <li>{v}</li> ))} </ul> )}
It seems a bit complicated in the preceding code, so let's take a look at what the code tries to achieve by looking at the result first:
return ( <ul> {[<li>Apple</li>, <li>Orange</li>]} </ul> )
Basically, it wants to output two li elements. To get there, we create an array containing two elements with a JavaScript expression. Once it becomes a JavaScript expression wrapped in brackets, {}, anything in JavaScript can be refactored and programmed however we want. We can use arr.map to form this array:
{['Apple', 'Orange'].map(v => ( <li>{v}</li> ))}
Well done in code refactoring!
There are just so many different brackets shown in the preceding statement, including {}, [], and (). So, feel free to take a moment to understand what each pair does. It is hard to believe that one of the challenges of writing in React is brackets.
This is a good example that shows you that once things are wrapped in a JavaScript expression, they can be refactored as we would normally program. In this case, we can take the arr outside the function since arr is a constant that doesn't have to be defined inside the Title component:
const arr = ['Apple', 'Orange']const Title = () => { return ( <ul> {arr.map((v) => ( <li>{v}</li> ))} </ul> )}
Once you get a feel for using the JavaScript expression along with HTML-like code, sooner or later, you will develop your own programming style because underlying this exercise is the JavaScript language.
Now that you have gotten to know this process, let's code an example together.
Example of a function component
A site is made up of pages, where each page contains a sidebar, a header, a content area, and a footer. All of them can be modeled with components. The layout component can sit at the top of the tree. When you zoom in, you find its children inside with a sub-structure. Just like a spider's web (see Figure 1.3) the tree structure cascades down from the outer level into the inner level.
Figure 1.3 – Web application layout
As UI engineers, we focus on the design of each component. Moreover, we pay close attention to the relationship between components. We want to know whether Title is built inside the main content or the sidebar. We want to know whether a header needs to be shared by multiple pages. You'll start to develop the skill to navigate between components among a tree.
Say we want to display a list of navigation links at the top of the page. Each link can be disabled if required. For enabled ones, we can click to navigate to its corresponding URL. See Figure 1.4:
Figure 1.4 – Nav component
The navigation links can be predefined in an array of link objects:
const menus = [ { key: 'home', label: 'Home' }, { key: 'product', label: 'Product' }, { key: 'about', label: 'About' }, { key: 'secure', label: 'Secure', disabled: true },]
In each of the preceding links, the key property provides an identifier, the label property specifies the displayed title, and the disabled property indicates whether the user is allowed to click on it or not.
We also want to display a line below the currently selected link. Based on these requirements, we come up with the implementation with selected and items props:
const Nav = ({ selected, items }) => { const isActive = item => item.key === selected const onClick = item => () => { window.location.href = item.url } return ...}
In the preceding Nav component, the items prop holds the list of links, and the selected prop holds the current selected item's key. The job of the Nav component is to display the list:
return ( <ul> {items.map(item => ( <li key={item.key} className={isActive(item) ? 'active' : ''} > <button disabled={item.disabled} onClick={onClick} > {item.label} </button> </li> ))} </ul> )
In the preceding return statement, items is iterated through one by one by following a loop and displaying links with a ul/li structure. Each link is displayed as a button supporting a disabled attribute. It also marks the link's CSS class as being active if it's the currently selected link.
Watch out for the key attribute for each item. This attribute is required for React to know the position of each li element among the lists. With the key provided as a unique identifier, React can quickly find the right element to perform the comparison and update the screen. key is a must-have attribute when returning an array of elements.
Playground – Nav Component
Feel free to play with the example online at https://codepen.io/windmaomao/pen/porzQjV.
Now we can display Nav with the following line. Voilà:
<Nav items={menus} selected="home" />
To make each menu item easy to develop and maintain, we can extract lines out to form a separate component:
const NavItem = ({ label, active, disabled, onClick}) => ( <li className={active ? 'active' : ''}> <button disabled={disabled} onClick={onClick}> {label} </button> </li>)
In the preceding code, a NavItem component is created to accept label, active, disabled, and onClick props. We don't need to overthink these prop names because they come in naturally, refactoring from the preceding Nav component. We can plug NavItem back to Nav:
const Nav = ({ selected, items }) => { const isActive = item => item.key === selected const onClick = item => () => { window.location.href = item.url } return ( <ul> {items.map(item => ( <NavItem key={item.key} label={item.label} disabled={item.disabled} active={isActive(item)} onClick={onClick(item)} /> ))} </ul> )}
This refactoring exercise is quite common and effective. This way, both Nav and NavItem components become easier to maintain in the future.
Summary
In this chapter, we first went over the history of UI components by looking at four libraries – jQuery, Angular, React, and LitElement – t o get an idea of having a component and how components are put together to build an application. Then, we learned what a function component is, with an introduction to its props and parent/child relationship. We then learned how to write a function component in general, and finally, we built a Nav component step by step.
In the next chapter, we will craft a state of the function component from scratch and see how actions can benefit from it.
Questions and answers
Here are some questions and answers to refresh your knowledge:
- What is a function component?
A function component is a function taking props as its input argument and returning elements. For an App component, we can display it by using its instance form, <App />. To build an application, it's about putting a component under another component as a child and refining this process until we end up with a tree of components.
- How do you write a function component?
The way to become adept at writing function components is quite similar to writing functions. Ask yourself what the props specification of the component is and what is returned for display. In a typical application, half of the components are designed for business requirements, but the other half normally comes from code refactoring. A study of Functional Programming (FP) can generally benefit you and take your UI skills to the next level.
Appendix
Appendix A – How many component types does React support?
In the published React documentation, it supports two component types. One is a function component, and another one is a class component. React supported the class component from the beginning:
class ClassComponent extends React.Component { render() { const { name } = this.props; return <h1>Hello, { name }</h1>;}}
Although the render function of a class component looks quite similar to what a function component returns and, most of the time, we can convert them in between, the class and function components are treated differently inside the React update process. Therefore, this book intentionally avoids mentioning the class component so as not to confuse any newcomer to React.
In general, a function component can be written shorter and simpler, and it's also easier in terms of development and testing because it has plain inputs and outputs. Also, it doesn't have the this keyword, which can intimidate new developers or sometimes even senior developers. However, the downside of using a function component is that it's relatively new to the programming world, and there's also a mentality shift from Object-Oriented Programming (OOP) to Functional Programming (FP), which can consume you if you are not prepared. Not to mention, being new means there can be different approaches that we need to learn and absorb before we can address the old problems.
Other than the class and function components, internally, React actually supports more component types, as in the following example:
import { memo } from 'react'const Title = memo(() => <h1>Hello</h1>)const App = () => <Title />
When the memo function is applied to the Title component, it creates a component with a component type, MemoComponent. We don't need to go into the details of these component types, but just know that each component type gets its own update algorithm when updated to the screen.