Tutorial

Replacing Component Lifecycles with the useEffect Hook, in React

Published on September 9, 2020
author

Holly Girouard

Replacing Component Lifecycles with the useEffect Hook, in React

Introduction

React Hooks are revolutionizing the way we develop in React and solving some of our biggest concerns. The useEffect Hook allows us to replace repetitive component lifecycle code.

Essentially, a Hook is a special function that allows you to “hook into” React features. Hooks are a great solution if you’ve previously written a functional component and realize that you need to add state to it.

If you’re new to Hooks and would like an overview, check out the introduction to React Hooks.

This article assumes that you’re familiar with the useState Hook. If you’re not, never fear! If you spend a little time with Convert a React Class-Based Component to a Functional One Using a State Hook you’ll be on the right track!

About useEffect

useEffect is short for ‘use side effect’. Effects are when our application reacts with the outside world, like working with an API. It allows us to run a function based on whether something changed. useEffect also allows us to combine componentDidMount and componentDidUpdate.

About Our App

We’ll be taking some prewritten class-based code and converting it to a functional component. We’ll be using reactstrap to simplify our formatting and axios to call an external dummy API.

Specifically, we’re using jsonplaceholder to pull-in dummy user data on our initial component mount.

Starter Code

Then we are triggering the component to re-render based on a user click and pulling in additional data about the users.

Starter Code, clicked

Getting Started

Just clone over the repo with the starting code:

$ git clone https://github.com/alligatorio/use-effect-hook
$ npm i
$ npm start

Take a moment to familiarize yourself with the code, in particular, the ClassBasedComponent.js file.

You’ll notice that we have two lifecycle methods in this file, componentDidMount and componentDidUpdate.

async componentDidMount() {
  const response = await axios
    .get(`https://jsonplaceholder.typicode.com/users`);

  this.setState({ users: response.data });
};

async componentDidUpdate(prevProps) {
  if (prevProps.resource !== this.props.resource) {
    const response = await axios
      .get(`https://jsonplaceholder.typicode.com/users`);

    this.setState({ users: response.data });
  }
};

These are both async lifecycle methods that call the jsonplaceholder API to bring in a list of users.

In componentDidMount, we say on first render, get the user data. Next, on componentDidUpdate we look to see if anything has changed in props. This can be triggered from user initiated events, like in our example, a button press. Once the change is detected we say, go out and get the data again.

We would like to condense the lifecycle methods into the useEffect Hook and create a function-based component.

Create a Component

Rather than using the same ClassBasedComponent.js file, create a new file called FunctionBasedComponent.js. We’re creating a new file so that we can contrast and compare the two.

In your terminal, you can run the following to create the new file from your root directory:

$ touch FunctionBasedComponent.js

To help get started, copy and paste the code below into your new file:

import React, { useState, useEffect } from 'react';
import { Container, Button, Row } from 'reactstrap';
import axios from 'axios';

const FunctionBasedComponent = () => {
  return (
    <Container className="user-list">
      <h1>My Contacts:</h1>
    </Container>
  )
};

export default FunctionBasedComponent;

Now hop over to your App.js file, import your FunctionBasedComponent.js file and replace ClassBasedComponent with FunctionBasedComponent.

Your app should now look something like the screenshot below.

our starting useEffect app


Let’s start by initializing state with useState.

const [ users, setUsers ] = useState([]);
const [ showDetails, setShowDetails ] = useState(false);

To quickly recap on useState, to initialize state with the useState hook, we declare both our variable and the function that corresponds to the variable in an array and then we pass useState() the argument that we’d like to initialize our variable with.

  • The users state variable is initialized with an empty array and given the function of setUsers.
  • The showDetails state variable is initialized with the value of false and assigned the function of setShowDetails.

Add an API Call

Let’s go ahead and add in our API call as the fetchUsers function.

const fetchUsers = async () => {
  const response = await axios.get(`https://jsonplaceholder.typicode.com/users`);

  setUsers(response.data);
};

We are essentially pulling this async call from the former componentDidMount and componentDidUpdate functions.

Keep in mind we cannot use an async function directly inside useEffect. If we ever want to call an async function, we need to define the function outside of useEffect and then call it within useEffect.

The useEffect Argument

Let’s talk about the useEffect hook for a moment. Much like componentDidMount, useEffect will immediately call our function.

useEffect( () => {}, [ 'value' ]);

By default, useEffect looks to see if the array values are different and if they are different, the arrow function is automatically called.

useEffect( () => {}, [ 'different value' ]);

Let’s flip back to our code editor and add the useEffect hook below our latest function where we will call fetchUsers.

In the code below, we’re looking at the users object to see if there are changes.

useEffect( () => { fetchUsers(users) }, [ users ] );

Common Issues

  • If you don’t pass an array into the useEffect Hook, your component will continuously reload repeatedly.
useEffect( () => { fetchUsers(users) } );
  • If you pass an empty array, we are not watching any variables, and therefore it will only update state on the first render, exactly like componentDidMount.
useEffect( () => { fetchUsers(users) }, [] );
  • Every time we create an object in JavaScript, it is a different object in memory. Though the code below looks the same, the page will be re-rendered because each object is stored in a different memory address. The same logic applies for arrays.
useEffect( () => { fetchUsers(users) }, [{ user: 'Alli Alligator' }] );

Is not equal to!

useEffect( () => { fetchUsers(users) }, [{ user: 'Alli Alligator' }] );
  • useEffect function must return a cleanup function or nothing.

To demonstrate triggering another re-render, copy and paste the code below into your FunctionBasedComponent.js file:

import React, { useState, useEffect } from 'react';
import { Container, Button, Row } from 'reactstrap';
import axios from 'axios';

const FunctionBasedComponent = () => {
  const [ users, setUsers ] = useState([]);
  const [ showDetails, setShowDetails ] = useState(false);

  const fetchUsers = async () => {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users`);

    setUsers(response.data);
  };

  useEffect( () => { fetchUsers(users) }, [ users ] );

  const handleClick = event => { setShowDetails(!showDetails) };

  return (
    <Container>
      {
        users.map((user) => (
          <ul key={ user.id }>
            <li>
              <strong>{ user.name }</strong>
              <div>
                <Button
                  onClick={ handleClick }
                >
                  { showDetails ? "Close Additional Info" : "More Info"  }
              </Button>
               { showDetails &&
                 <Container className="additional-info">
                   <Row>
                     { `Email: ${ user.email }` }
                   </Row>
                   <Row>
                     { `Phone: ${ user.phone }` }
                   </Row>
                   <Row>
                     { `Website: ${ user.website }` }
                   </Row>
                 </Container>
               }
              </div>
            </li>
          </ul>
        ))
      }
    </Container>
  )
}

export default FunctionBasedComponent;

Now we have an onClick event within a button. On the button click, the state of showDetails is changed, triggering a re-render that will call again to the API and bring in the additional details that we need.

Voilà!

async componentDidMount() {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users`)
    this.setState({ users: response.data })
};

async componentDidUpdate(prevProps) {
  if (prevProps.resource !== this.props.resource) {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users`)
    this.setState({ users: response.data })
  }
};

Becomes:

const fetchUsers = async () => {
  const response = await axios.get(`https://jsonplaceholder.typicode.com/users`);

  setUsers(response.data);
};

useEffect( () => { fetchUsers(users) }, [ users ] );

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Holly Girouard

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
1 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Not sue why you are so excited by this. The class version can be written like this, and is much clearer in my view:

async fetchUsers(){ const response = await axios.get(https://jsonplaceholder.typicode.com/users) this.setState({ users: response.data }) }

async componentDidMount() {fetchUsers();}

async componentDidUpdate(prevProps) { if (prevProps.resource !== this.props.resource)fetchUsers(); };

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.