Back button to all articlesAll articles

Introduction to React Hooks

React Hooks have been on everyone's mind for a while now, and now that the hype has died down, I thought it would be nice to write a short introduction on them and go through concrete use-cases.

React Hooks have been introduced with React version 16.8, they allow us to use features that were once reserved to class components (such as internal state, lifecycle hooks etc.) in functional components.

This is great, as writing functional components is often preferred by the community as they offer advantages, namely: code that is easier to read and maintain, easier to test and often following better practices. For example, it's easier to create presentational, container and business logic functional components than it is using a class-based components.

Today, we'll only cover two of the most used hooks: useState and useEffect.

Make sure you have a grasp of React's fundamentals to take full advantage of this lesson, especially the concept of internal state and lifecycle hooks.

To follow along, feel free to clone the following repository where we'll transform class components into functional components using these two hooks. These components can be found under /components/ExampleUS and /components/ExampleUE.

<br> <br>

useState

Alright, we have the following class-based component:

1class ExampleUS extends React.Component { 2 state = { 3 value: "", 4 }; 5 6 onChange = (event) => { 7 this.setState({ 8 value: event.target.value, 9 }); 10 }; 11 12 render() { 13 return ( 14 <article> 15 <h1>useState example</h1> 16 17 <input type="text" value={this.state.value} onChange={this.onChange} /> 18 19 <p>Value: {this.state.value}</p> 20 </article> 21 ); 22 } 23} 24

All it does is allow the user to input something, which is saved in the components internal state and displayed bellow, like so:

<div style="display: flex; align-items: center; justify-content: center"> <img width="50%" alt="render of the component mentioned" src="https://user-images.githubusercontent.com/15229355/57177519-cb69bc00-6e64-11e9-9871-5cf31f4c874e.png"> </div>

This component requires an internal state, so using a class-based approach made sense before 16.8, but the useState hook will allow us to transform it into its functional counterpart.

<br>

useState syntax

The useState syntax is very easy to grasp:

1const [value, setValue] = useState(""); 2

Where value is the variable to which we will bind the state, setState is the method to be called to update it and the parameter passed to useState is the state's default value. Pretty easy, right?

<br>

Transforming the component

Going from a class component to a functional one will take 2 easy steps:

  1. First, we change the declaration of the component into a functional one
1// Changed the declaration of the component 2const ExampleUS = () => { 3 state = { 4 value: "", 5 }; 6 7 // onChange is now assigned to a constant variable 8 const onChange = (event) => { 9 this.setState({ 10 value: event.target.value, 11 }); 12 }; 13 14 // Removed the render method, 15 // Functional components directly return the JSX to be rendered 16 return ( 17 <article> 18 <h1>useState example</h1> 19 <input type="text" value={this.state.value} onChange={this.onChange} /> 20 <p>Value: {this.state.value}</p> 21 </article> 22 ); 23}; 24
  1. Let's now remove all traces of the class' context (this) and state
1const ExampleUS = () => { 2 // Removed the state declaration 3 4 // Removed the call to this.setState() 5 const onChange = (event) => {}; 6 7 // Removed all calls to the context 8 return ( 9 <article> 10 <h1>useState example</h1> 11 <input type="text" onChange={onChange} /> 12 <p>Value:</p> 13 </article> 14 ); 15}; 16
<br>

The final result

Alright, we can now use useState with the syntax mentioned before to create an internal state.

Here's what the final component looks like (don't forget to import the hook):

1import React, { useState } from "react"; 2 3const ExampleUS = () => { 4 // We declare the state and the method to update it 5 const [value, setValue] = useState(""); 6 7 // On input, call setValue with the new state value 8 const onChange = (event) => { 9 setValue(event.target.value); 10 }; 11 12 // Bind the input to the state value and display it 13 return ( 14 <article> 15 <h1>useState example</h1> 16 <input type="text" value={value} onChange={onChange} /> 17 <p>Value: {value}</p> 18 </article> 19 ); 20}; 21
<br> <br>

useEffect

For this example, we have the following component:

1class ExampleUE extends React.Component { 2 state = { 3 url: "", 4 }; 5 6 /** 7 * Fetch a random dog photo and save its URL in our state 8 */ 9 componentDidMount() { 10 fetch("https://dog.ceo/api/breeds/image/random") 11 .then((res) => res.json()) 12 .then((data) => 13 this.setState({ 14 url: data.message, 15 }) 16 ); 17 } 18 19 render() { 20 return ( 21 <article> 22 <h1>useEffect example</h1> 23 <img src={this.state.url} alt="dog picture" /> 24 </article> 25 ); 26 } 27} 28

Where, on mount, we fetch a picture, save it in the internal state and display it, it looks something like this:

<div style="display: flex; align-items: center; justify-content: center"> <img width="50%" alt="render of the component mentioned" src="https://user-images.githubusercontent.com/15229355/57177873-0110a400-6e69-11e9-820e-727f47c5192d.png"> </div>

The focal point being the lifecycle hook componentDidMount that is called whenever the component is mounted (meaning whenever it is inserted into the DOM tree). We will use the useEffect hook to do the exact same thing but in a functional component.

<br>

useEffect syntax

Once again, this hook's syntax is easy to understand and use:

1useEffect(() => { 2 // ... 3}); 4

It takes as its first parameter a callback that will be triggered each time the component is rendered.

But in our case, we only wish to trigger it once, when the component is mounted, right?
To do so, we can pass useEffect a second parameter, an array of variables that will trigger the callback only when they are modified (instead of triggering it at every render of the component). We can also pass an empty array ([]) to tell the callback to be triggered only on mount and dismount of the component, making it look like so:

1useEffect(() => { 2 // ... 3}, []); 4
<br>

Transforming the component

We'll skip this part, as it doesn't change much from the previous iteration.

<br>

The final result

1// Don't forget to import both hooks 2import React, { useState, useEffect } from "react"; 3 4const ExampleUE = () => { 5 const [url, setUrl] = useState(""); 6 7 // On component mount, the callback is called 8 // Fetch retrieves a picture and saves it in our internal state 9 // The second parameter tells useEffect 10 // to only be triggered on mount and dismount 11 useEffect(() => { 12 fetch("https://dog.ceo/api/breeds/image/random") 13 .then((res) => res.json()) 14 .then((data) => setUrl(data.message)); 15 }, []); 16 17 return ( 18 <article> 19 <h1>useEffect example</h1> 20 <img src={url} alt="dog picture" /> 21 </article> 22 ); 23}; 24
<br> <br>

Wrapping up

React Hooks are a great addition to the library, they provide considerable advantages and make the developer experience much smoother.
One important thing to note is that there are many other hooks, some more used than others and I invite you to read up on the official documentation as it is very well produced.

Other references include:

Thank you for reading, if you've learned something feel free to follow me on Twitter @christo_kade as I'll share all my new blog posts about React, Vue and the JS ecosystem as a whole ❤️