Using React’s useEffect like a pro

Alexander Leon
4 min readJul 17, 2021

Hey guys, in this walkthrough I’ll be showing you everything you need to know to use React’s useEffect function like a pro.

A brief history

Before there was useEffect, there were lifecycle methods, namely:

  1. componentDidMount
  2. componentDidUpdate
  3. componentWillUnmount

There are a couple others, but these are the main ones. componentDidMount is triggered immediately after the component‘s HTML has rendered. componentDidUpdate is triggered whenever state or props have been updated. componentDidUpdate exposes the previous state and props so you could compare and contrast the changes and make decisions based on that info. for instance

componentDidUpdate(prevProps, prevState) {
if (prevProps.foo !== this.props.foo) {/* do something */}
if (prevState.bar !== this.state.bar) {/* do something else */}
}

componentWillUnmount, triggered right before the component is unmounted, is commonly used for unsubscribing from listeners or any other clean-up you may need.

With these lifecycle methods, we can reliably handle any change to the component’s state, so why the change to useEffect?

Enter React Stateless Components

At one point (I believe as early as 2016), the React team made a decision to create React components that can be instantiated with just a regular, old-school, Javascript function. So:

import React from "react";function MyFunc() {
return <div>Hey!</div>
}

No more this, no more React.Component, no more render() {}. Unfortunately for the lifecycle methods, those methods were injected into the component alongside React.Component so a new approach had to be created to allow users to handle state changes.

Enter React.useEffect

The new approach to manage state changes came in the form of a useEffect function exposed by the React library. So how do we re-create our beloved componentDidMount, componentDidUpdate, and componentWillUnmount methods? Let’s expand on our earlier stateless function:

import React from "react";function MyFunc(props) {  const [bar setBar] = React.useState(null);

React.useEffect(() => {
console.log("componentDidMount");
return () => {
console.log("componentDidUnmount");
}
}, []);

React.useEffect(() => {
console.log("componentDidUpdate when 'bar' changes");
}, [bar]);
React.useEffect(() => {
console.log("componentDidUpdate when 'props.foo' changes");
}, [props.foo]);
return <div>Hey!</div>
}

If you take a moment to examine the code, you’ll see that we’ve setup events for all the state changes as we did with the previous approach using lifecycle methods. The variables we put inside the arrays, by the way, are called dependencies; so the dependencies of the useEffect function. One minor gotcha to keep it mind is that regardless of how many dependencies you add to the useEffect function (if any), the useEffect function will trigger on mount, so make sure to account for that.

One other thing we added to this example, React.useState, a state hook, is out of scope for this tutorial, but learn more about it here!

Going beyond lifecycle management

It’s great that we can manage a component’s state as well as with the previous lifecycle methods. Between the simplicity and reusability of stateless functions, and the React.useEffect function, a new paradigm has become available to us: reusable stateful functions, or more commonly named: custom hooks.

A savvy reader like yourself probably noticed that with useEffect, we can’t access the prevProps or prevState property like we could before. Fear not! Read the solution for that here. The reason this solution works is because React.useRef does not trigger re-renders of the component. Additionally, useEffect without dependencies triggers every time there’s a re-render of the component regardless of what state-change triggered it.

Hello Custom Hooks

There’s really no better base example to a custom hook than the one React shares in their official documentation here. Let’s start at the bottom of the example and move our way up:

function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}

The purpose of this stateless function is clear: flip the friend’s color to green or black depending on if they’re online. But how do we get there?

import React, { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);

function handleStatusChange(status) {
setIsOnline(status.isOnline);
}

useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});

return isOnline;
}

They number one thing to look at here is the return value. Rather than HTML, as we usually do with stateless components, in this case we’re returning a boolean. The number two thing to observe is our input value. Rather than passing in props like we would do in a stateless component, we are passing in a value — friendID. After that, we are simply using useEffect to listen for changes to the online status of said user and we’re keeping track of the current status using the state hook isOnline.

Unrelated PSA: Looking for a new high paying software development job? Send me your resume to alexleondeveloper@gmail.com and I’ll get back to you!

Conclusion

In this tutorial, we reviewed:

  1. How we managed a component’s lifecycle before useEffect and why that approach became deprecated
  2. How we can use useEffect to effectively manage a component’s lifecycle
  3. How we can take advantage of the reusability of React’s stateless functions and useEffect to create reusable stateful functions.

If you found this article helpful, make sure to clap! Catch you on the flip side.

--

--

Alexander Leon

I help developers with easy-to-understand technical articles encompassing System Design, Algorithms, Cutting-Edge tech, and misc.