Synchronizing with useEffect

The last big concept I want to introduce in this series is how to use the useEffect Hook.

Effects can be difficult to wrap your brain around, so don't expect to understand it all in one sitting.

Earlier in this series, we covered the useState Hook, and if you aren't familiar with Hooks, I recommend you start there.

It's often the case that we will need to synchronize our React apps with external systems such as the DOM, APIs, logging, or listening for changes on a server.

In this article, we will cover how to manage synchronization with Effects.

Synchronizing with useEffect

What are Effects?

In React, we have two events that the UI reacts to:

  • When we render the code - When we take our state and props and transform and return the JSX we want to see on the screen.
  • By triggering event handlers - Such as a button or an input field that we can react to trigger a state change (and inform us that we should update our UI).

Effects in React refer to the side effects that occur due to rendering, updating, or unmounting components (unmounting is when a component is removed from the screen).

Side effects can include data fetching, subscriptions, manual DOM manipulation, and other activities that don't directly affect the component's output.

React provides a built-in Hook called useEffect to help manage these side effects cleanly, efficiently, and declaratively.

When to use Effects?

There are 3 use cases we might want to hook into:

  • When a component has "mounted", meaning that the component has completed rendering for the first time.
  • If a component has been updated
  • The component is about to be removed from the screen

It might be surprising, but with useEffect, we can cover all of these scenarios.

How to Use useEffect

The useEffect Hook takes two arguments: a function where you can place your side effect logic and an optional array of dependencies that will cause the function to re-run.

Let's build it up step by step:

  1. We will Declare your effect.
  2. Specify the dependencies (when the effect should run).
  3. Optionally, you can add a cleanup function if needed.

1) Declare your Effect

We declare an effect in our component by importing the useEffect Hook from React and calling it in the top level of our component.

Here's a simple example:

import { useEffect } from 'react';

function TestComponent() {
  useEffect(() => {
    // Code here will run after *every* render
  });
  return <div>Hello world</div>;
}

Every time your component renders, your screen will be updated, and then will run the code inside your useEffect function.

2) Specify dependencies

Without specifying dependencies, our code will run after every render, which might be unnecessary.

If you want your effect to run just once when the component first renders, we can pass it an empty array as its dependency:

useEffect(() => {
  // Empty dependency array means this effect runs only once on mount
}, []); // 👈 Empty array as second parameter

Or, if you want it to run when a certain piece of state has been updated we can do it like this:

function MyExample() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Clicked ${count} times`;
  }, [count]); // Only re-run the effect if count state changes

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

3) Cleanup when necessary

In this example, we will add an event listener in our useEffect to listen for window resizing:

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

function MyExample() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      console.log("handleResize:", window.innerWidth)
      setWidth(window.innerWidth);
    }
    window.addEventListener('resize', handleResize);
  }, []); // Empty dependency array means this effect runs once on mount 

  return <div>Window width: {width}</div>;
}

There is a major problem with this code; we now have an event listener that will continue running long after removing the component since we are setting it to the window object and not just locally in our React application.

To fix this, we need to remove the event listener before we remove the component.

To fix the issue, return a cleanup function from your Effect.

We do this by returning a function at the end of our effects function like this:

function MyExample() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);

    window.addEventListener('resize', handleResize);
    // This function is only called right before the component is about to be removed
    return () => {
      // Cleanup and remove the event listener ✅
      // This return is only called right before the component is about to be removed
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Empty dependency array means this effect runs once on mount and cleanup on unmount

  return <div>Window width: {width}</div>;
}

In this example, an event listener is added when the component mounts and it is cleaned up (or removed) right before the component is removed.


The useEffect Hook is a powerful tool in React that allows developers to manage side effects in their functional components.

It is an essential part of the React Hooks API, which empowers developers to write fully functional components with less complexity and more clarity.

As you continue your journey in React, you'll find useEffect invaluable in building scalable and maintainable applications. 🦾

In the next article we will cover how to style your components using CSS modules.

https://www.codu.co/articles/styling-with-css-modules-bkwmwnvp


Follow me on Twitter or connect on LinkedIn.

🚨 Want to make friends and learn from peers? You can join our free web developer community here. 🎉

FrontendTutorialLibraryReactjsJavaScript
Avatar for Niall Maher

Written by Niall Maher

Founder of Codú - The web developer community! I've worked in nearly every corner of technology businesses: Lead Developer, Software Architect, Product Manager, CTO, and now happily a Founder.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.