Understanding the useEffect Hook in React

Avinash Vagh - Jul 9 '22 - - Dev Community

use-Effect Hook

You can perform data fetching, subscriptions, or manually changing the DOM from React components before.

We call these operations “side effects” (or “effects” for short) because they can affect other components and can’t be done during rendering.

The Effect Hook, useEffect, adds the ability to perform side effects from a function component.

It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API. ( We will have a comparison later )

You can also think of it as an after render method.

use-Effect hook

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate: 
   useEffect(() => {    
       // Update the document title using the browser API    
       document.title = `You clicked ${count} times`;  
    });

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

use-Effect hook

When you call useEffect, you’re telling React to run your “effect” function after flushing changes to the DOM.

Effects are declared inside the component so they have access to its props and state.

By default, React runs the effects after every render — including the first render

useEffect(() => {    
    // writing cleanups
    let timer = setInterval(()=>setCount(count=>count+1),1000)
    return ()=>{
        // write cleanups here 
        // will be executed before component unmounts
        clearInterval(timer)
    }
    });
Enter fullscreen mode Exit fullscreen mode

cleanup

The clean-up function runs before the component is removed from the UI to prevent memory leaks. Additionally, if a component renders multiple times (as they typically do), the previous effect is cleaned up before executing the next effect. In our example, this means a new subscription is created on every update.
Docs

updates

Both useState and useReducer Hooks bail out of updates if the next value is the same as the previous one. Mutating state in place and calling setState will not cause a re-render.

Normally, you shouldn’t mutate local state in React. However, as an escape hatch, you can use an incrementing counter to force a re-render even if the state has not changed:

const [ignored, forceUpdate] = useState(x => x + 1, 0);

  function handleClick() {
    forceUpdate();
  }
Enter fullscreen mode Exit fullscreen mode

Phases in React components

Link


Effect

Effects let you run some code after rendering so that you can synchronize your component with some system outside of React.

  • What are effects and how are they different from events?
  • Before getting to effects, you need to be familiar with two types of logic inside React components:

Rendering code (introduced in Describing the UI) lives at the top level of your component. This is where you take the props and state, transform them, and return the JSX you want to see on the screen. Rendering code must be pure. Like a math formula, it should only calculate the result, but not do anything else.

Event handlers (introduced in Adding Interactivity) are nested functions inside your components that do things rather than just calculate them. An event handler might update an input field, submit an HTTP POST request to buy a product, or navigate the user to another screen. Event handlers contain “side effects” (they change the program’s state) and are caused by a specific user action (for example, a button click or typing).

Sometimes this isn’t enough. Consider a ChatRoom component that must connect to the chat server whenever it’s visible on the screen. Connecting to a server is not a pure calculation (it’s a side effect) so it can’t happen during rendering. However, there is no single particular event like a click that causes ChatRoom to be displayed.

Effects let you specify side effects that are caused by rendering itself, rather than by a particular event. Sending a message in the chat is an event because it is directly caused by the user clicking a specific button. However, setting up a server connection is an effect because it needs to happen regardless of which interaction caused the component to appear. Effects run at the end of the rendering process after the screen updates. This is a good time to synchronize the React components with some external system (like network or a third-party library).

Here and later in this text, “effects” refers to the React-specific definition above, i.e. side effects caused by rendering. When we want to refer to the broader programming concept, we’ll say “side effects”.

How to write an effect

To write an effect, follow these three steps:

  1. Declare an effect. By default, your effect will run after every render.

  2. Specify the effect dependencies. Most effects should only re-run when needed rather than after every render. For example, a fade-in animation should only trigger when a component appears. Connecting and disconnecting to a chat room should only happen when the component appears and disappears, or when the chat room changes. You will learn how to control this by specifying dependencies.

  3. Add cleanup if needed. Some effects need to specify how to stop, undo, or clean up whatever they were doing. For example, “connect” needs “disconnect,” “subscribe” needs “unsubscribe,” and “fetch” needs either “cancel” or “ignore”. You will learn how to do this by returning a cleanup function.

Step 1: Declare an effect

To declare an effect in your component, import the useEffect Hook from React: import { useEffect } from 'react'; Then, call it at the top level of your component and put some code inside your effect:

function MyComponent() {
  useEffect(() => {
    // Code here will run after *every* render
  });
  return <div />;
}
Enter fullscreen mode Exit fullscreen mode

Every time your component renders, React will update the screen and then run the code inside useEffect. In other words, useEffect “delays” a piece of code from running until that render is reflected on the screen

!ERROR
By default, effects run after every render. This is why code like this will produce an infinite loop:

const [count, setCount] = useState(0);
useEffect(() => {
  setCount(count + 1);
});
Enter fullscreen mode Exit fullscreen mode

Step 2: Specify the effect dependencies

By default, effects run after every render. Often, this is not what you want:

You can tell React to skip unnecessarily re-running the effect by specifying an array of dependencies as the second argument to the useEffect call. Start by adding an empty [] array

useEffect(() => {
    // ...
  }, []);
Enter fullscreen mode Exit fullscreen mode

The behaviors without the dependency array and with an empty [] dependency array are very different:

useEffect(() => {
  // This runs after every render
});

useEffect(() => {
  // This runs only on mount (when the component appears)
}, []);

useEffect(() => {
  // This runs on mount *and also* if either a or b have changed since the last render
}, [a, b]);
Enter fullscreen mode Exit fullscreen mode

Step 3: Add cleanup if needed

Consider a different example. You’re writing a ChatRoom component that needs to connect to the chat server when it appears. You are given a createConnection() API that returns an object with connect() and disconnect() methods. How do you keep the component connected while it is displayed to the user?

useEffect(() => {
  const connection = createConnection();
  connection.connect();
}, []);
Enter fullscreen mode Exit fullscreen mode

Imagine the ChatRoom component is a part of a larger app with many different screens. The user starts their journey on the ChatRoom page. The component mounts and calls connection.connect(). Then imagine the user navigates to another screen—for example, to the Settings page. The ChatRoom component unmounts. Finally, the user clicks Back and ChatRoom mounts again. This would set up a second connection—but the first connection was never destroyed! As the user navigates across the app, the connections would keep piling up.

Bugs like this are easy to miss without extensive manual testing. To help you spot them quickly, in development React remounts every component once immediately after its initial mount. Seeing the "Connecting..." log twice helps you notice the real issue: your code doesn’t close the connection when the component unmounts. This is done with the help of StrictMode

To fix the issue, return a cleanup function from your effect:

useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, []);
Enter fullscreen mode Exit fullscreen mode

How to handle the effect firing twice in development?

React intentionally remounts your components in development to help you find bugs like in the last example. The right question isn’t “how to run an effect once,” but “how to fix my effect so that it works after remounting”.

Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the effect running once (as in production) and an effect → cleanup → effect sequence (as you’d see in development).

Examples

  • Subscribing to events
useEffect(() => {
  function handleScroll(e) {
    console.log(e.clientX, e.clientY);
  }
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, []);
Enter fullscreen mode Exit fullscreen mode
  • analytics
useEffect(() => {
  logVisit(url); // Sends a POST request
}, [url]);
Enter fullscreen mode Exit fullscreen mode
  • Chat Room
export default function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Welcome to {roomId}!</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Summary

  • Unlike events, effects are caused by rendering itself rather than a particular interaction.
  • Effects let you synchronize a component with some external system (third-party API, network, etc).
  • By default, effects run after every render (including the initial one).
  • React will skip the effect if all of its dependencies have the same values as during the last render.
  • You can’t “choose” your dependencies. They are determined by the code inside the effect.
  • An empty dependency array ([]) corresponds to the component “mounting”, i.e. being added to the screen.
  • When Strict Mode is on, React mounts components twice (in development only!) to stress-test your effects.
  • If your effect breaks because of remounting, you need to implement a cleanup function. React will call your cleanup function before the effect runs next time, and during the unmount.

If you found this article useful, you can follow me on Twitter & connect with me on LinkedIn & comment down your thoughts here.

. . . . . . . . . . . . . . . . . . . . . . .