What are components?

After elements, perhaps the next most important concept in React is that of components. In this chapter, we shall unravel all the theoretical and practical details of working with components in React, starting with a detailed discussion of what exactly a component is.

At the core:

A component is a reusable piece of the UI.

It's not very difficult to think of a component in this way.

If you've ever spent some time designing a web app's wireframe, even on a piece of paper, you might've already used the idea of components without realizing about it.

For instance, let's suppose you are sketching the wireframe of a simple blog site. Like all sites, you place a header at the very top of the site. This header is a component. It's a reusable part of the UI, although you won't be using the header elsewhere (a site has only one header, right?).

A component can itself be made up of other components.

Going back to the same header example above, you might've drawn a box representing the logo and another box representing the navigation bar inside the header. These boxes further denote components, i.e. a logo component and a nav component. The logo component might be used inside the footer of the blog as well — an instance of component reuse.

When designing a web app we almost always think in terms of these discrete pieces of the UI. They are very natural to our thought process of designing the app.

React takes this idea a step further and proposes to think in terms of components when programming a web app as well. This has the benefit that as soon as the design stage completes, we already have a pretty decent overview of what components to create in React.

Anyways, let's now see the technical definition of a component to build the .

A component in React is a function, or a class with a render() method, that returns a React node.

Back in the day, React components used to be denoted as classes (ES6-style). But after the advent of React 16.8, this changed — a new way of creating components was introduced and that via functions.

This is the reason of including both a function and a class in the definition above.

Nowadays, functions are the recommended way of creating components in React for reasons which we'll understand later on in this chapter.

Creating and instantiating components

As mentioned above, just a minute ago, there are two kinds of components in React: function components and class components.

Creating a function component is simply done by creating a function, whose name is capitalized, and that returns back a React node.

Similarly, creating a class component is done by creating an ES6-style class, once again whose name is capitalized, and that has a render() method returning back a React node.

So here are the two ways of creating a Greeting component.

First, as a function component:

function Greeting() {
   return (
      <h1>Hello World!</h1>
   );
}

And now, as a class component:

class Greeting {
   render() {
      return (
         <h1>Hello World!</h1>
      );
   }
}

Absolutely easy.

Now, at this stage, it's crucial to know that a component on its own can't be used in the user interface. It is merely a blueprint for an actual piece of the UI.

This is easy to think in terms of a class in OOP. A class, supposed to be denoting a component here, is merely a blueprint for that piece of UI — nothing real exists in the app just by creating the class. A concrete instance of the class is only created when it is instantiated.

This instantiation of a component (be it a function or a class) is done by creating a React element out of it. Thereafter, the created element is referred to as the component's instance.

Precisely speaking, React apps are built out of React elements, which represent component instances, besides DOM elements, as discussed in the previous-to-previous chapter, React Elements.

Creating a component vs. a component instance

It's very important to know of the difference between creating a component and creating a component instance.

The component is created by constructing a function or class in JavaScript, while a component instance is created by invoking createElement() and passing in the component as the first argument, or equivalently by creating a JSX element for that component.

Resources out there tend to mix up both these terms. They loosely claim that creating a component means to create a JSX element for that component, which is technically wrong.

It's always nice and preferable to use terminology carefully.

A component can be instantiated as many times as we like to.

Alright, so let's now instantiate our Greeting component, using JSX:

function Greeting() {
   return (
      <h1>Hello World!</h1>
   );
}

const element = <Greeting/>;

As you already know by now, <Greeting/> gets converted to the following createElement() call:

React.createElement(Greeting)

Now, as soon as this code is run, we have a living Greeting component instance rendered inside the #root element, all thanks to the instantiation <Greeting/>.

So what do you say, wasn't it easy to instantiate a component?

Working with props

When components are used, they are sparingly used without props.

Let's now change our Greeting component to render a <h1> that greets the user with his/her name. This name is passed as a prop to the component.

function Greeting(props) {
   return (
      <h1>Hello {props.name}</h1>
   );
}

With this in place, the following <Greeting> instance would say 'Hello Dennis':

function Greeting(props) {
   return (
      <h1>Hello {props.name}</h1>
   );
}

const element = <Greeting name="Dennis" />;

Let's understand what's going on in Greeting.

The props parameter of a function component contains the props passed in while instantiating that component. In particular, props is an object containing all the provided props.

In our case, we pass in a name prop to the Greeting component while instantiating it. This prop is accessed as props.name in the component.

We can use this Greeting component as many times as we want to.

Following we demonstrate creating two Greeting instances, nested inside a <div> element:

function Greeting(props) {
   return (
      <h1>Hello {props.name}</h1>
   );
}

const element = (
   <div>
      <Greeting name="Dennis" />
      <Greeting name="Ada" />
   </div>
);
Live Example

See how effective components are at code reuse. Using components, we can simplify our apps by creating the minimal number of these reusable pieces and then using wherever the need be.

Purely remarkable.

Now when accessing props in a component, there is a widely used convention. That is to destructure the object into local variables named after the given props.

Applying this idea to our Greeting component, here's how we could define the function:

function Greeting({name}) {
   return (
      <h1>Hello {name}</h1>
   );
}
Live Example

The {name} expression inside the pair of parentheses (()), at the start of the function's definition, creates a local variable called name and set its value to the name prop.

If we have two props, we would have two destructured properties and, thus, two variables:

function Greeting({name, language}) {
   return (
      <div>
         <h1>Hello {name}.</h1>
         <p>You are programming in {language}.</p>
      </div>
   );
}

If we have three props, we'd have three variables; if four props, then four variables; and so on and so forth.

At one point, you might say that this won't be scalable in the longer run. What if we have 20 props? Would we create each one of these as a separate variable?

Well, in such a case, we could leverage the rest operator (...). The idea is to create variables for very important props while group the rest of them into a separate object using the rest operator.

An example follows:

function Greeting({name, language, ...lessImportantProps}) {
   return (
      <div>
         <h1>Hello {name}.</h1>
         <p>You are programming in {language}.</p>
         <p {...lessImportantProps} />
      </div>
   );
}

Here, the name and language props are useful for the Greeting component, and hence used to create local variables, while the rest of the props are meant only for the second <p> element and thus grouped into lessImportantProps and then this object passed to the <p> element.

Obviously, in a real app, we won't call this object as lessImportantProps — this is only used for the sake of explanation. The common convention is to just call it props as shown below:

function Greeting({name, language, ...props}) {
   return (
      <div>
         <h1>Hello {name}.</h1>
         <p>You are programming in {language}.</p>
         <p {...props} />
      </div>
   );
}

As you'll start creating more complex components, composed of other components, you'll realize that this approach is extremely useful.

In particular, the props that are used by a component are used to create local variables in it while the remaining props are passed down to the sub-components, using another spread ... syntax that we discovered in the previous chapter, React JSX.

Prop drilling

As we learnt before, components can be made out of components themselves. Now, if we want to pass on props to those sub-components, we obviously need to start off passing props from the top-level component.

Let's see what this means.

Suppose we have a Greeting component made up of a heading and a body. The heading is denoted using a GreetingHeading component while the body is denoted using GreetingBody component.

Here's the definition of GreetingHeading:

function GreetingHeading({name}) {
   return (
      <h1>Hello, {name}.</h1>
   );
}

Here's the definition of GreetingBody:

function GreetingBody({language}) {
   return (
      <p>You are programming in {language}.</p>
   );
}

Now, let's see the composition of the Greeting component:

function Greeting({name, language}) {
   return (
      <div>
         <GreetingHeading name={name} />
         <GreetingBody language={language} />
      </div>
   );
}

It returns back a <div> holding the two elements <GreetingHeading> and <GreetingBody>.

Later on, we shall explore the special tags <> and </>, denoting fragments in React, that could be used in place of a <div> to group multiple React elements into one single element, in order to prevent using a <div> where there is no need to.

Let's now instantiate a Greeting element:

const element = (
   <Greeting name="Dennis" language="C" />
);

And now let's see the output produced:

Live Example

As can be seen, the <h1> and <p> elements indeed get rendered with the correct text inside them. This is the potential of props.

We pass on the name and isFirst props to Greeting which then passes them onto GreetingHeading and GreetingBody, each of which then use the prop passed to it for returning given content.

In this case, we had just one level of nesting inside Greeting. However, we could have any nesting depth as we want to.

And whatever the nesting be, if lower-level components require given props and if those props are passed on to the top-level components, then those props have to be passed down at each new level to desired lower-level components.

In fact, there's a special term to refer to it: prop drilling.

Prop drilling is basically passing over props from higher-level to lower-level components, and so on, until those props reach the very components they are consumed by.

Prop drilling obviously also means that we pass certain props to components that have absolutely nothing to do with those components themselves — they merely take them for the sake of passing them over to lower-level components.

This might not be a big issue for even some complex apps, but there would eventually come a point where it does start to become one. In that case, we could use contexts. We'll learn more about contexts later on in this course.

Using the children prop

Suppose we have a Button component that takes in the text to be put inside a <button> element and then returns back that element.

Something along the following lines.

function Button({text}) {
   return (
      <button className="btn">{text}</button>
   );
}

Using this Button component, we can easily create a custom button in our app:

/* ... */

const element = (
   <Button text="This is a custom button." />
);

Live Example

Button here is really handy. The entire logic of a button sits nicely inside the component. If we want to say, for example, that all buttons in the app should have the class app-btn, we just need to go to the Button component and make this change.

So clearly, abstracting away the creation of <button> elements directly with the help of a Button component is highly convenient in a large app.

But notice that currently we're only capable of providing content of the returned <button> via the text prop of Button. This isn't how we're used to working with buttons in HTML.

Recall that, in HTML, we are used to doing the following to add content inside a <button>:

<button>Some <b>important</b> text.</button>

The content of the button is provided between the opening and closing tags, as the button's children.

Fortunately, we could emulate this same syntax using React components as well, all thanks to the children prop.

In the React Elements chapter, we learnt that the children prop holds all of the children elements passed to a given element at the time of its creation.

The idea here is to retrieve the value of the children prop inside a component and then assign it to the children prop of another element while creating it, inside the component.

Simple.

Let's redefine our <Button> component to work this way:

function Button({children}) {
   return (
      <button className="btn">{children}</button>
   );
}

The children prop is rendered as the content of the returned <button> element, which simply means that the children of a <Button> element effectively become the children of the returned <button> element.

Now, let's try creating two <Button>s in our app:

/* ... */

const element = (
   <div>
      <Button>This is a custom button.</Button>
      <br/><br/>
      <Button>Some <b>important</b> text.</Button>
   </div>
);

The first button has just some plain text while the second one has another element in it.

The <br/> tag here obviously denotes the <br> HTML element, and is only used to be able to separate apart both the buttons as they're painted on the screen.

Live Example

As you can see in the link above, each renderd button indeed has the content shown above.

This is nothing but amazing!

The defacto App component

Let's now talk about structuring our React applications in a way that sits with the convention.

The file where the bundling begins is index.js (or index.jsx if you're using JSX files) when then imports an App component from another file called App.js (in the same src directory).

index.js looks something along the following lines:

index.js
import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

ReactDOM.createRoot(document.querySelector('#root')).render(
   <App/>
);

This App.js file defines an App component that is the entry into our app. More precisely, it's the top-most element in React's virtual DOM tree living inside the #root element.

App.js
import React from 'react';

// The entry into our app.
export default function App() {
   return (
      <h1>Hello World!</h1>
   );
}

The file index.js serves to call on to other libraries such as React DOM, while App.js defines the entry into the app without having to worry much about these external dependencies.

In this approach, it's important not to mix up the purpose of index.js with that of App.js.

From this point onwards, in this course, we'll be making the following assumption before every single piece of code, unless stated otherwise.

That is, there would be a main App component where we'd be putting our JSX code, and then this App component would get rendered inside the #root DOM element.

Very basic!

Working with state

It's now to talk about working with state in components (function components)

As mentioned before, stateful function components are a relatively new introduction to React. And so, in order to get them to mirror class components from previous version of React, React decided to introduce a hooking feature whereby developers would have to manually hook a given function component into a given utility.

For the state utility, this hook is called useState().

When useState() is called inside a component, the component hooks into React's state utility. But more specifically, calling useState() instructs React to internally start managing state data for every instance created of that component.

We can't have direct access to this state data outside of the component — it's managed by React itself and only accessible via useState().

How exactly useState() works is quite interesting.

useState() accepts an optional argument which initializes the state datum to the given value. For example, if we call useState(10), the data gets initialized to 10; if we call useState(false), it gets initialized to false; and so on and so forth. We could pass literally any valid value in JavaScript here.

useState() returns back an array containing two elements:

  • The first element holds the state data.
  • The second element is a function to mutate the state.

Conventionally, the return value of useState() is destructured into local variables, where the name of the variable representing the second element, i.e. the state-update function, begins with the word 'set' followed by the name of the state variable.

When useState() is called for the very first time, for a given component instance, it initializes the state for the instance for that particular call of useState(). Thereafter, every same call to useState() returns the existing state datum.

Let's consider two examples to help understand this better.

In the following example, we create a simple counter program. There is an <h1> representing the counter's count, with a <button> to increment the count; nothing really difficult to code:

import React, { useState } from 'react';

function Counter() {
   const [count, setCount] = useState(0);
   return (
      <div>
         <h1>{count}</h1>
         <button onClick={() => { setCount(count + 1) }}>Increment</button>
      </div>
   );
}

function App() {
   return <Counter/>;
}

Live Example

The counter's count becomes state data of the Counter component, and gets named simply as count. The corresponding state-update function is named setCount().

Since the counter begins at 0, useState(0) initializes the count to 0, to begin with. The count gets rendered inside the <h1> element. The button's onClick handler calls setCount() with the next count, i.e. count + 1. This call effectively updates the state datum and issues a re-render of the Counter instance.

The re-render causes Counter to be invoked again, which obviously causes useState() to be invoked again as well; however, this time, useState() resolves with an array holding the latest state data, i.e. 1, and not the initial value 0.

And this goes on and on, as the counter is incremented.

Time to move over to our second example.

Below, we have a more complex timer, with a reset button, an increment button and a decrement button. Clicking the reset button sets the count state back to 0, the decrement button decrements it, while the increment button obviously increments it:

import React, { useState } from 'react';

function Counter() {
   const [count, setCount] = useState(0);
   return (
      <div>
         <h1>{count}</h1>
         <button onClick={() => { setCount(0) }}>Reset</button>
         <button onClick={() => { setCount(count - 1) }}>Decrement</button>
         <button onClick={() => { setCount(count + 1) }}>Increment</button>
      </div>
   );
}

function App() {
   return <Counter/>;
}

Live Example

The counter is fully-functional but there's a slight problem with it. The moment the count becomes 0, we are still able to decrement past that point to get a count of -1, and so on. This must be prevented.

Fortunately, doing so only requires us to set up a simple conditional inside the onClick handler of the decrement button. The following code accomplishes this idea:

function Counter() {
   const [count, setCount] = useState(0);
   return (
      <div>
         <h1>{count}</h1>
         <button onClick={() => { setCount(0) }}>Reset</button>
         <button onClick={() => { count !== 0 && setCount(count - 1) }}>Decrement</button>
         <button onClick={() => { setCount(count + 1) }}>Increment</button>
      </div>
   );
}

Live Example

Amazing!

If you realize it, two new features have been added to our counter like literally in the span of seconds, all thanks to the way state works in React and how React is designed, in general.

In contrast, if we were implementing this same counter program in vanilla JavaScript, we'd have to take some time laying out the imperative statements of the DOM to add these two new reset and decrement features.

React is just next-level!