React.js: A functional component and its state

In the previous blog, I generated a new React/TypeScript app using the create-react-app tool. In this blog, you’ll get familiar with two types of React components, and what a component’s state is for.

Two types of React components

A React component can be declared either as a function or as a class. A functional component is implemented as a function and is structured as shown below (types are omitted).

const MyComponent = (props) => {  // 1

  // other functions may go here
  
  return ( 
    <div>...</div>   // 2
  )
}

export default MyComponent;

1. props is used to pass data to the components
2. Return the component’s JSX

Developers who prefer working with classes can create a class-based component, which is implemented as a subclass of React.Component and is structured as follows:

class MyComponent extends Component {   // 1

  render() {   // 2
    return (   // 3
      <div>...</div>
    );
  }

  // other methods may go here
}

export default MyComponent;

1. The class must be inherited from React.Component
2. The render() method is invoked by React
3. Return JSX for rendering

If a functional component would simply return JSX, the class-based one has to include the method render() that returns JSX. I prefer using functional components, which have several benefits over the class-based ones:

* A function is easier to read than a class and requires less code to write; there is no need to inherit the component’s code from any class either
* A functional component generates less code during Babel transpiling
* No instances are created for wrapping the function
* No need for the this reference
* Functions are easier to test than classes; assertions simply map props to the returned JSX

You should use class-based components only if you have to use the React version older than 16.8. In old versions, only the class-based components would support the component’s state and lifecycle methods.

If you use the current version of create-react-app with the –typescript option, the generated file App.tsx file will already have the boilerplate code of a functional component (i.e. a function of type React.FC) shown below.

import React from 'react';    // 1
import logo from './logo.svg';
import './App.css';

const App: React.FC = () => {  // 2
  return (    // 3
    <div className="App">  // 4
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;   // 5

1. Import React library
2. This is a functional component
3. Return the component’s template as a JSX expression ( it’s not a string)
4. In JSX, use className instead of the CSS selector class to avoid conflicts with the JavaScript keyword class
5. Export the component declaration so it can be used in other modules

I used version 3.0 of create-react-app. The older versions of this tool would generate a class-based component App. The generated App function returns the markup (or template), which React uses for rendering this component’s UI. During the build process, Babel will convert it into a pure JavaScript object JSX.element with the div container that will update the Virtual DOM (I’ll introduce it in a separate blog) and the browser’s DOM. This App component didn’t have a separate place for storing its data (a.k.a. state), and we’ll add it in the next section.

Managing the component’s state

A component’s state is the datastore that contains the data that should be rendered by the component. The data in the component’s state is preserved even if React re-renders the component. Whenever the code updates the component’s state, React updates the component’s UI to reflect changes caused by the user’s actions (e.g. button clicks or typing in the input fields) or other events. If you have a Search component, its state can store the last search criteria and the last search result.

Do not confuse the component’s state with the application’s state. The former is the storage of an individual component’s state while the latter stores the app’s data that may represent the data from multiple components, functions, or classes.

And how would you define and update the component’s state? This depends on how the component was created in the first place. We’re going to move back to class-based components for a minute so that you can understand the difference in dealing with state in class-based and functional approaches. Then we’ll return to functional components, which I recommend using.

Adding state to a class-based component

If you have to work with a class-based component, you could define a type representing the state, create and initialize an object of this type, and then update it as needed by invoking this.setState(…).

Let’s consider a simple class-based component that has a state object with two properties: the user name and the image to be displayed. For serving images we’ll use the web site called Lorem Picsum, which returns random images of the specified size. For example, if you enter the URL https://picsum.photos/600/150, the browser will show a random image having the width 600px and height 150px. The next listing shows such a class-based component with a two-property state object.

interface State { <1>
  userName: string;
  imageUrl: string;
}

export default class App extends Component {

  state: State = { userName: 'John', <2>
           imageUrl: 'https://picsum.photos/600/150' }; 

  render() {
    return (
      <div>
        <h1>{this.state.userName}</h1> <3>
        <img src={this.state.imageUrl} alt=""/> <4>
      </div>
    );
  }
}

1 Defining the type for the component’s state
2 Initializing the State object
3 Rendering the userName here
4 Rendering the imageUrl here

By looking at the code of the render() method, you can guess that this component would render John and the image. Note that we embedded the values of the state properties into JSX by placing them inside the curly braces, e.g. {this.state.userName}.

Any class-based component is inherited from the class Component, which has a property state and the method setState(). If you need to change the value of any state property, you must do it using this method, for example:

this.setState({userName: "Mary"});

By invoking setState() you let React know that the UI update may be required. If you update the state directly (e.g. this.state.userName=’Mary’), React won’t call the method render() to update the UI. As you might have guessed, the state property is declared on the base class Component.

Earlier, I listed the benefits of functional components over the class-based ones, and we won’t use class-based components any longer. In functional components, we manage state by using hooks introduced in React 16.8.

Using hooks for managing state in functional components

In general, hooks allow to “attach” behavior to a functional component without the need to write classes, create wrappers or use inheritance. It’s as if you say to a functional component, “I want you to have additional functionality while remaining a flat function”.

Hooks have their names started with the word use. For example, useState() is the name of the hook for managing the component’s state, while useEffect() is used for adding a side-effect behavior (e.g. fetching data from a server). In this section, we’ll focus on the useState() hook using the same example as in the previous section: a component whose state is represented by the user name and the image URL, but this time it’ll be a functional component.

The useState() hook allows you to store the state in one or more primitive variables as well as in an object. The following line shows you how to define a state for the user name.

const [userName, setUserName] = useState('John'); 

The function useState() returns a pair: the current state value and a function that lets you update it. Do you remember the syntax of array destructuring introduced in ECMAScript 6? The above line means that the hook useState() takes the string ‘John’ as an initial value and returns an array, and I use destructuring to get the two elements of this array into two variables: userName and setUserName. The syntax of array destructuring allows you to give any names to these variables. If you need to update the value of userName from John to Mary and make React to update the UI (if needed), do it as follows:

setUserName('Mary');

In your IDE, do CMD-Click or Ctrl-Click on the useState(), and it’ll open the type definition of this function, which will declare that this function returns a stateful value and a function to update it. The function useState() is not a pure function because it stores states somewhere inside React. It’s a function with side effects.

The next listing shows a functional component that stores the state in two primitives: userName and imageUrl and displays their values using JSX.

import React, {useState} from 'react'; <1>

const App: React.FC = () => {

  const [userName, setUserName] = useState('John');  <2>
  const [imageUrl, setImageUrl] = useState('https://picsum.photos/600/150'); <3>
  
  return (
    <div>
      <h1>{userName}</h1> <4>
      <img src={imageUrl} alt=""/>  <5>
    </div>
  );
}

export default App;

1 Importing the useState hook
2 Defining the userName state
3 Defining the imageUrl state
4 Rendering the value of the state variable userName
5 Rendering the value of the state variable imageUrl

Now let’s re-write the component from the previous listing so instead of two primitives, it’ll declare its state as an object with two properties: userName and imageUrl. The next listing declares an interface State and uses the useState() hook to work with the object of type State.

import React, {useState} from 'react';

interface State {   // 1
  userName: string;
  imageUrl: string;
}

const App: React.FC = () => {

  const [state, setState] = useState<State>({  // 2
    userName: 'John',
    imageUrl: 'https://picsum.photos/600/150'
  });  

  return (
    <div>
      <h1>{state.userName}</h1>  // 3
      <img src={state.imageUrl} alt=""/>  // 4 
    </div>
  );
}

export default App;

1. Defining the type for the component state
2. Defining and initializing the state object
3. Rendering the value of the state property userName
4. Rendering the value of the state property imageUrl

Note that the useState() is a generic function and during its invocation, I provided the concrete type State.

The source code of this sample app is located in the directory hello-world. Run the command npm start and the browser will render the window that look similar to the screenshot below (the image may be different though).

The user name and image are too close to the left border of the window, which is easy to fix with CSS. The generated app shown earlier had a separate file App.css with CSS selectors applied in the component with the className attribute; you can’t use the class attribute to avoid a conflict with the reserved JavaScript keyword class. This time, we’ll add the margin by declaring a JavaScript object with styles and using it in JSX. In the next listing, I added the variable myStyles and used it in the component’s JSX.

const App: React.FC = () => {

  const [state, setState] = useState<State>({
    userName: 'John',
    imageUrl: 'https://picsum.photos/600/150'
  });  

  const myStyles = {margin: 40};   // 1

  return (
    <div style ={myStyles}>       // 2
      <h1>{state.userName}</h1> 
      <img src={state.imageUrl} alt=""/> 
    </div>
  );
}

1. Declaring the styles
2. Applying the styles

With this margin, the browser will render the div with additional 40px of space around it as shown in the next screenshot.

Our first React app works and looks nice! It has one functional component that stores hard-coded data in the state object and renders them using JSX. It’s a good start, and in the next blog, we’ll start writing a new app that will have more functionality.

If you want to learn TypeScript quickly, get our book TypeScript Quickly.

4 thoughts on “React.js: A functional component and its state

  1. Can you explain how to use the setState to update in this example the userName? You only ever call the state values but don’t set them after they are initialized.

Leave a comment