Why I Love React’s useEffect() Hook

Ghameerah (Juh-meer-ruh) McCullers
11 min readJul 25, 2023

--

Hello there, my name is Ghameerah McCullers & I’m JavaScript Engineer here to talk with you today about why I fell in love with React’s useEffect() Hook.

I’ll do my best to get into the nuances of the feature without getting too in the weeds so even React newbies can grasp it. For starters, you should check out the new React official documentation. React’s site has a really great guide on how to get started with React Hooks for beginners.

Also, to get your feet wet, you should play around with the Tic Tac Toe tutorial provided by the official React website. Building this mini-project should give you hands-on experience with the tool. Let’s get started!

I’m assuming you already have a decent understanding of JavaScript and React concepts. If you’re still getting acquainted with these topics no worries, I highly recommend reading the Thinking in React blog post in order to boost your familiarity.

In order to get the most from this post, you’ll need to have a solid understanding of “lifecycle methods” in React. Grasping React’s concepts can be a challenge even for experienced engineers, however, this can be accomplished by studying React’s documentation and practicing.

One thing that attracts me to React is the ability it gives you to solve almost any programming challenge while considering the nuances of the web & the browser. In this article, you’ll learn what makes the useEffect() Hook is so handy. Let’s dive into my favorite reasons to love useEffect()!

It makes me truly happy when I see a programming tool being used in an elegant way. Being a pragmatic programmer has forced me to realize the importance of good coding practices. If you’re a programmer who cares about learning your field then this article is for you.

When I was first introduced to React it took a lot of perseverance to get a true understanding of the framework’s coding style. Learning how the framework has evolved since 2018 has kept me interested and curious. When I was first studying React, my introduction to components was “Class-based”, which now are considered outdated. The transition from “Class-based” components to “Function-based” components has given developers room to build applications that are far more flexible and scalable. In order to understand why “Function-based” components are heavily favored you have to have a core understanding of “functional programming”.

Functional programs

You may be familiar with different coding paradigms. JavaScript supports several programming paradigms — procedural, object-oriented, and functional. Of these, ”functional programming” offers developers a more effective way of writing readable, maintainable code. Functional programming uses functions as the fundamental building block for constructing software while creating more fluidity in our programs. Since React was built on “Class-based” components, having an understanding of object-oriented programming served me when I was first learning about the framework. Historically, each component was a Class but OOP (object-oriented programming) in JavaScript is super confusing. Before we had Hooks and functional components we had to use Class-based components. “Classes confuse both people and machines”, quoted directly from React’s documentation. Classes make code hard to reuse and introduce difficulties to code organization.

Who doesn’t like less code?

Simply put, the relevancy of classes dwindled when it came to the future of front-end technology. The community agreed that we can do more without classes. Typically, you will define components as functions instead. You can read about the Motivation for Functional Components in the official legacy documentation. When writing code in a functional style, there’s less code compared to class components. This makes our code more readable. Well, that’s the idea at least. In functional programming, a function is expected to be “pure”, meaning it should have no side effects!

But to really understand what a side effect is, we first have to grasp the concept of a pure function.

Functions Should Have no Side Effects

You may not know this, most React components are intended to be “pure functions”. A side effect is when a function alters the state of an external variable. Pure functions are only allowed to return a value; they are not allowed to alter the state of the system in any other way. A pure function can read an external variable, but it should not change it! When we do everything with functions, its intention is to avoid side effects.

Why we should avoid side effects

In pure functions, the only thing that matters is its input. This helps developers avoid mutability by making a new data structure. This prevents programmers from accidentally changing a value. Functional programming deters away from side effects. It creates, not mutates. But we still need a way to update data. This is why side effects are such a critical part of functional programming. Hooks in React ADD side-effects in function components. So for example, when we change state, based on some outside thing this is known as a side effect. In React, Hooks are functions that give class-like abilities to function components, including state and side effects. The very popular useEffect() Hook is used for performing an after-effect when certain changes occur in state(s).

React wasn’t built with Hooks; they are a more recent development. You can read more about Effect Hooks on React’s site. Personally, when I was first introduced to Hooks it was during my second engineering role.

React Hooks

There are a few rules when using Hooks, including the following:

  • Hooks must be called from React functions (i.e. components or custom Hooks) and not from a regular JavaScript function (i.e. Class-based components).
  • Hooks should not be called inside of a loop, condition, or nested function. Instead, Hooks should be called at the top level of a functional component.

React Effect Hooks control when parts of the DOM are rendered or (re-drawn). Basically an Effect Hook “listens” for changes in order to trigger a response. You can read about the origins of the useEffect() Hook by visiting React’s docs.

What is useEffect() and why is it useful?

The useEffect() Hook takes in a function and an array of dependencies. This Hook registers a function to be treated as an "effect." This Hook can be used to run side effects (call to an external API, update another state, etc.) or attach event listeners. The effect runs the first time the component is rendered. If the elements inside the array have changed from the previous render, the effect can run after the current render process finishes. Essentially, this Hook will run when some other action happens, typically the state or states within the application. The Hook needs to know which piece(s) of state to listen for; this is what is referred to as the dependency list or array. The dependency array will be included in the second argument. This Hook doesn’t return any value. Let’s take a look at an example:

useEffect(
() => {
// Runs side effect here
},
[] // Array of dependencies
);

A dependency array can help to prevent infinite loops. Be careful, if you update the

state in the effect but also have that piece of state in your dependency array, then your dependency array will cause an infinite loop. If you do not provide the dependencies array at all and only provide a function to useEffect(), it will run after every render.

The code above accepts an empty array [] as a second argument. When we leave the array empty, the effect will only run once irrespective of the changes to the state it is attached to. The key to running an effect once is to pass in an empty array.

What exactly is an “effect”?

When I was first learning React, I was confused about the name of the Hook. The word effect refers to the functional programming term I mentioned earlier, a “side effect”. This Hook allows developers to Hook into the ability to use effects in our function components. Side effects are basically something that happens when something else happens.

How to attach an effect to a particular state

The Effect Hook registers code to run only at certain times. Typically we want to invoke this function at the time the state is updated. Adding a piece of state to the dependency is the way the Effect function is only invoked when the state value actually changes. The intended usage for the Effect function is for side effects so we want to attach the Hook to the state we want to listen for. If there is no dependence array, the Effect function gets evoked for any state change. If a dependency array is passed, the Hook is only invoked initially, and then only when at least one of the values in the dependency array has changed. Let’s take a look at an example:

const Counter = () => {
const [value, setValue] = useState(0);

useEffect(() => {
console.log(`The value has changed to ${value}`)
}, [value]);

The code above accepts an array [value] as a second argument. The console will only log “The value has changed to ${value}” when value is updated.

The function passed to useEffect() is a callback function. This will be called after the component renders.

Why after the render: Rendering in React under the hood

The Effect Hook runs on every render and re-render. So whenever the state changes in your component or receives new props, it will re-render and cause the Effect Hook to run again. I think it’s worth touching on rendering in React and the render() method. The render() method in React enables us to render a React element onto a root node, so we can display it on the screen. Typically, we render our components onto the <div id="root"></div> in index.js. It is important to understand that the root node is not replaced, but rather, the content is inserted (or appended) into the container. When components are rendered they’re added to the DOM. If you’re an experienced React or JavaScript programmer, you should be familiar with the DOM. React uses Virtual DOM, which can be thought of as a copy of the DOM. When any changes are made to React elements, the Virtual DOM is updated. The Virtual DOM finds the differences between it and the DOM and re-renders only the elements in the DOM that changed. This makes the Virtual DOM faster and more efficient than updating the entire DOM. You should also be familiar with ReactDOM and how it enables us to see our components in the browser. The content described in the render() method contains a React element after the return. Calling ReactDOM.render() with two parameters will insert the React element into the given container.

useEffect() vs. lifecycle methods

The useEffect() Hook includes functionality that are found in the componentDidMount(), componentDidUpdate(), and componentWillUnmount() lifecycle methods. For many use cases, defining componentDidMount(), componentDidUpdate(), and componentWillUnmount() together in class components is equivalent to calling useEffect() in function components. All these lifecycle methods have now been combined into a single Hook. Instead of using three methods in a class-based component, you only need to use a single Hook in a functional-based component, which takes effect in similar places where the class-component methods are used. The trick is to use particular function parameters and return values that are intended for the useEffect() Hook.

Lifecycle methods

When using Hooks in functional components, it’s equivalent to using React lifecycle methods like componentDidMount(), componentDidUpdate(), and so on.

React component lifecycle

Let’s compare a class-based component with how we use the useEffect() Hook:

import React, { Component } from 'react';

class App extends Component {
componentDidMount() {
console.log('I have just mounted!');
}

render() {
return <div>Insert JSX here</div>;
}
}

And now using useEffect():

const App = () => {
useEffect(() => {
console.log('I have just mounted!');
});

return <div>Insert JSX here</div>;
}

Notice in the above code snippet that there was only one argument passed, so the Effect will only be run after every re-

render. Let’s touch more on Running an effect once (previously componentDidMount()). In order to do that we need to better understand mounting in React. When a component is rendered to the DOM for the first time it is called “mounting” in React. We can declare special methods on Class components to run some code when a component mounts and unmounts. These methods are called “lifecycle methods”. The componentDidMount() method runs after the component output has been rendered to the DOM.

componentDidMount()

If you define the componentDidMount() method, React will call it when your component is added (mounted) to the screen. This is a common place to start data fetching, set up subscriptions, or manipulate the DOM nodes.

If you implement componentDidMount(), you must implement other lifecycle methods to avoid bugs. For example, if componentDidMount() reads some state or props, you also have to implement componentDidUpdate() to handle their changes, and componentWillUnmount() to clean up whatever componentDidMount() was doing.

Unmounting in React

Whenever the DOM element produced by a component is removed this is called “unmounting” in React. Essentially, when a component is removed from the DOM, it has been “unmounted”. We can use the componentWillUnmount() lifecycle method in order to handle any side effects when a component was unmounted.

componentWillUnmount() (Cleanup)

The callback function passed to the Effect Hook as the first parameter may also return a “cleanup function” that is executed before the next scheduled effect runs and just before any additional effect calls are triggered by the dependency array. This can be used to remove event listeners or abort an API call.

To run the Effect Hook as the component is about to unmount, we just have to return a function from the useEffect() Hook:

useEffect(
() => {
// Runs side effect here
return () => {
// Do clean up here
};
},
[] // Array of dependencies
);

Using effects when things change (componentDidUpdate())

If the state changes within the class component we need to make use of the componentDidUpdate() life cycle method to handle their changes. This would be the equivalent of calling useEffect() in function components, where we could pass an optional dependency array that causes the Effect Hook to run.

I’d like to thank Sid for creating this cool graphic that visualizes the flow of control withuseEffect() vs. lifecycle methods:

React lifecycle methods in Hooks

I trust that you took some great nuggets away from the article. I touched on Functional programming, side effects, Hooks in React, and class components vs. functional components.

Most importantly hope you learned something about React and the useEffect() Hook in particular. This was personally the toughest part of studying React. I felt more like a React Developer once I grasped the concept of life cycle methods and the motivation for Hooks.

Follow me for more posts! LinkedIn | Twitter | GitHub

--

--