React 17 update focus - useEffect's clean up function timing changes

Written byKalanKalan
💡

If you have any questions or feedback, pleasefill out this form

This post is translated by ChatGPT and originally written in Mandarin, so there may be some inaccuracies or mistakes.

(This screenshot is taken from the official React blog)

React 17 has reached the RC (Release Candidate) stage. The official blog announcement states that this release does not include major updates or new features. However, the article does highlight some interesting points that I'd like to share.

This article will not go through each update individually but will instead focus on the more noteworthy aspects.

Changes in Event Delegation Nodes

In React, when using an event listener like <button onClick={}>, React places all event listeners on the document rather than on the node itself. This technique is known as Event Delegation and helps avoid performance issues caused by a large number of event listeners. After React 17, listeners will be moved to the root element instead of the document. Since this is managed within React, it becomes easier to add new features later, such as the Replay Event mentioned in the post.

Removal of Event Pooling

To improve performance, prior to React 17, an Event Pooling mechanism was used, which implemented a custom Synthetic Event. However, according to the official statement, the performance improvements in modern browsers are actually limited, and it can also lead to confusion for developers. For example, if e.target is passed as a parameter to another component, it might turn out to be null. This is due to React's design choice aimed at enhancing performance. To use it correctly, one had to call e.persist() to ensure that React does not manipulate it.

With React 17, the Event Pooling mechanism has been completely removed, so there's no need to call e.persist() anymore, which is a relief. Some articles or tutorials may emphasize React's Event Pooling design; this may need to be updated in the future.

Major Change: Timing of useEffect's Cleanup Function

This might be the most significant change in this update.

When using useEffect, most effects are unrelated to screen updates, so React will execute the effect after the screen has been updated.

The overall sequence will look like this:

Component updates → DOM changes accordingly → Screen updates → Execute effect

This avoids situations where tasks within the effect might consume too much time and potentially impact DOM rendering. For example:

const App = () => {
  useEffect(() => {
    longlongTask(); // This will take a long time
    console.log('Hello World');
  })
  return <Component />
}

Given the sequence mentioned above, React ensures that other effects are executed only after the screen updates, thus preserving user experience.

However, in some scenarios, you may want to execute an effect before the screen updates to prevent visual jumps. In such cases, you can use useLayoutEffect instead.

The above behavior was expected in React 16, so let's review it again. Now, let's discuss the cleanup part.

In React 16, even though the useEffect effects are executed after the screen updates, the cleanup function is executed before the screen updates. Generally speaking, this has little impact, as most cleanup functions just involve unsubscribe, removing listeners, or canceling APIs. However, if the cleanup function takes too long, it can affect screen updates.

Consider this example:

const App = () => {
  useEffect(() => {
    longlongTask(); // This will take a long time, but that's okay; React will execute it after the screen updates
    console.log('Hello World');
    return () => {
      longlongTask(); // The screen will freeze until longlongTask() finishes (in React 16)
    }
  })
  return <Component />
}

In React 16, the timing for cleanup execution is as follows (when the component is updating):

Component updates → DOM changes accordingly → Execute cleanup function → Screen updates → Execute effect

After React 17, the timing for cleanup execution has also changed to be after the screen updates (when the component is updating):

Component updates → DOM changes accordingly → Screen updates → Execute cleanup functionExecute effect

If it's about component unmounting:

React 16: Component updates → Execute cleanup function → DOM changes accordingly → Screen updates

React 17: Component updates → DOM changes accordingly → Screen updates → Execute cleanup function

Cleanup Functions Execute in Order of useEffect

React 17 executes the cleanup functions in the same order as the effects, according to their position in the tree. Previously, this order was occasionally different.

While it's unclear in what situations cleanup functions might have a dependency on order, it seems that in React 16, the timing of cleanup execution occasionally led to unexpected sequences.

If you found this article helpful, please consider buying me a coffee ☕ It'll make my ordinary day shine ✨

Buy me a coffee