VDone Demo VDone Demo
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)

Nikolay Tuzov

Backend Developer
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)
  • 核心概念

  • 高级指引

  • Hook

    • Hooks Overview
    • Using the State Hook
    • Using the Effect Hook
      • Effects Without Cleanup
        • Using Classes
        • Using Hooks
        • Key Difference
      • Effects with Cleanup
        • Using Classes
        • Using Hooks
      • Tips for Using Effects
        • Purpose of Hooks
        • Tip: Use Multiple Effects to Separate Concerns
        • Explanation: Why Effects Run on Each Update
        • Tip: Optimizing Performance by Skipping Effects
    • Rules of Hooks
    • Custom Hooks
  • 案例演示

  • 《React》笔记
  • Hook
xugaoyi
2021-04-06
Contents

Using the Effect Hook

# 03. Using the Effect Hook (Side Effect Hook)

If you're familiar with React class lifecycle methods, you can think of the useEffect Hook as componentDidMount (mount complete), componentDidUpdate (update complete), and componentWillUnmount (before unmount) combined.

Did: has done... Will: is about to...

Data fetching, setting up subscriptions, and manually changing the DOM in React components are all examples of side effects. Whether or not you're used to calling these operations "side effects" (or just "effects"), you've likely performed them in your components before.

There are two common kinds of side effects in React components: those that require cleanup and those that don't. Let's look at this distinction in more detail.

# Effects Without Cleanup

Sometimes, we want to run some additional code after React has updated the DOM. Network requests, manual DOM mutations, and logging are common examples of effects that don't require cleanup. We say that because we can run them and immediately forget about them. Let's compare how classes and Hooks let us express such side effects.

# Using Classes

In React class components, the render function itself shouldn't cause side effects. In general, it's too early -- we typically want to perform our effects after React has updated the DOM.

This is why in React classes we put side effects into componentDidMount and componentDidUpdate. Coming back to our example, here is a React counter class component that updates the document title right after React makes changes to the DOM:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  // DOM mounted
  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  // DOM updated
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Note how we have to duplicate the code between these two lifecycle methods in class.

This is because in many cases we want to perform the same side effect regardless of whether the component just mounted, or if it has been updated. Conceptually, we want it to happen after every render -- but React class components don't have such a method. Even if we extracted a separate method, we would still have to call it in two places.

Now let's see how we can do the same with the useEffect Hook.

# Using Hooks

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


  useEffect(() => { // This callback runs after every component DOM render (i.e., mount complete and update complete lifecycle)
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we'll refer to it as our "effect"), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.

Why is useEffect called inside a component? Placing useEffect inside the component lets us access the count state variable (or any props) right from the effect. We don't need a special API to read it -- it's already in the function scope. Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution.

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this (opens new window).) Instead of thinking in terms of "mounting" and "updating", you might find it easier to think that effects happen "after render". React guarantees the DOM has been updated by the time it runs the effects.

# Key Difference

Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don't block the browser from updating the screen. This makes your app feel more responsive. The majority of effects don't need to happen synchronously. In the uncommon cases where they do (such as measuring the layout), there is a separate useLayoutEffect (opens new window) Hook with an API identical to useEffect.

# Effects with Cleanup

Earlier, we looked at how to express side effects that don't require any cleanup. However, some effects do. For example, we might want to set up a subscription to some external data source. In that case, it is important to clean up so that we don't introduce a memory leak! Let's compare how we can do it with Classes and with Hooks.

# Using Classes

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  // After mount
  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  // Before unmount
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# Using Hooks

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => { // This callback runs after every component DOM render (mount complete and update complete lifecycle)
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Runs before every re-render (class componentWillUnmount only runs when unmounting)
    return function cleanup() { // The function name is not required, arrow functions can be used
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Tips for Using Effects

# Purpose of Hooks

One of the purposes (opens new window) of Hooks is to solve the problem where class lifecycle methods often contain unrelated logic, while related logic gets split across several different methods.

# Tip: Use Multiple Effects to Separate Concerns

You can use multiple effects to separate unrelated logic into different effects:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Explanation: Why Effects Run on Each Update

If you've used classes, you might wonder why the effect cleanup phase happens after every re-render, and not just once during unmounting. Let's look at a practical example to see why this design helps us create components with fewer bugs.

...

Forgetting to handle componentDidUpdate properly is a common source of bugs in React applications.

...

# Tip: Optimizing Performance by Skipping Effects

In some cases, cleaning up or applying the effect after every render might create a performance problem.

If certain values haven't changed between re-renders, you can tell React to skip applying an effect by passing an array as an optional second argument to useEffect:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
1
2
3

This also works for effects with cleanup:

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // Only re-subscribe if props.friend.id changes
1
2
3
4
5
6
7
8
9
10

In the future, the second argument might be added automatically by a build-time transformation.

Notes:

  1. If you use this optimization, make sure the array includes all values from the outer scope that change over time and are used by the effect, otherwise your code will reference stale values from previous renders.

  2. If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as the second argument.

  3. If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better (opens new window) solutions (opens new window) to avoid re-running effects too often. Also, don't forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.

  4. We recommend enabling the exhaustive-deps (opens new window) rule as part of our eslint-plugin-react-hooks (opens new window) package. It warns when dependencies are specified incorrectly and suggests a fix.

Edit (opens new window)
Last Updated: 2026/03/21, 12:14:36
Using the State Hook
Rules of Hooks

← Using the State Hook Rules of Hooks→

Recent Updates
01
How I Discovered Disposable Email — A True Story
06-12
02
Animations in Grid Layout
09-15
03
Renaming a Git Branch
08-11
More Articles >
Theme by VDone | Copyright © 2026-2026 Nikolay Tuzov | MIT License | Telegram
  • Auto
  • Light Mode
  • Dark Mode
  • Reading Mode