React 17 Key Highlights - Timing Change for the useEffect Cleanup Function
(Screenshot taken from the official React blog)
React 17 has reached the RC stage. The official blog post states that this release contains no major updates or new features. However, there are still some interesting tidbits to be found in the article, which I’d like to share here.
This article won’t go over every single update, but will instead document the parts that are more worthy of attention.
Changes to the Event Delegation Node
In React, when using an event listener like <button onClick={}>, React attaches all event listeners to document instead of the node itself. This technique is known as Event Delegation, and its benefit is avoiding the performance issues caused by a massive number of event listeners. Starting with React 17, listeners will be attached to the root element instead of document. Because it’s now under React’s direct control, it will be easier to introduce new features down the road, such as the Replay Events mentioned in the tweet.
Removal of the Event Pooling Mechanism
To improve performance, React used an Event Pooling mechanism prior to React 17—essentially its own custom implementation of SyntheticEvent. However, according to the official statement, the performance gains it brings in modern browsers are actually limited, and it also caused confusion for some developers. For instance, if you wanted to pass e.target as an argument to another component, you would find that it unexpectedly turned into null. This was a deliberate design choice made by React to boost performance. To use it properly, you had to explicitly call e.persist() to ensure React wouldn’t tamper with it.
In React 17, the Event Pooling mechanism has been completely removed, so there’s no need to call e.persist() anymore—awesome! Some articles or tutorials used to emphasize React’s Event Pooling design, which may need to be updated going forward.
Star This: Timing Change for the useEffect Cleanup Function
This is probably the most significant change in this release.
When using useEffect, since most effects are not directly related to screen updates, React runs effects after the screen has updated (after painting).
Overall, the flow looks like this:
Component updates → DOM changes accordingly → Screen updates → Run effect
This prevents computationally heavy work inside the effect from blocking the DOM rendering. For example:
const App = () => {
useEffect(() => {
longlongTask(); // 我會跑很久哦
console.log('Hello World');
})
return <Component />
}
Because of the flow mentioned above, React ensures that other effects only run after the screen has updated, avoiding a degraded user experience.
However, in certain scenarios where you want the effect to run before the screen updates to prevent visual flickering, you can use useLayoutEffect instead.
The above was the expected behavior in React 16—just a quick recap. Now let’s talk about the cleanup part.
In React 16, although the effect itself in useEffect ran after the screen updated, the cleanup function actually ran before the screen updated. Usually this didn’t have much impact, as most cleanup functions only perform things like unsubscribe, removing event listeners, or canceling API requests. However, if the cleanup function took too much time, it could noticeably affect the screen update.
Suppose you write it like this:
const App = () => {
useEffect(() => {
longlongTask(); // 我會跑很久哦,但沒關係 react 會在畫面更新後才執行
console.log('Hello World');
return () => {
longlongTask(); // 畫面會卡住直到 longlongTask() 結束(在 React 16 當中)
}
})
return <Component />
}
In React 16, the execution timing for cleanup was like this (when a component updates):
Component updates → DOM changes accordingly → Run cleanup function → Screen updates → Run effect
Starting in React 17, the timing for cleanup has also changed to after the screen updates (when a component updates):
Component updates → DOM changes accordingly → Screen updates → Run cleanup function → Run effect
If the component unmounts:
React 16: Component updates → Run cleanup function → DOM changes accordingly → Screen updates
React 17: Component updates → DOM changes accordingly → Screen updates → Run cleanup function
Cleanup Functions Now Run in the 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 hard to imagine situations where cleanup functions would require a specific order of dependency, in React 16 the execution order of cleanup functions seemed to occasionally be unexpected.
Related Posts
- Recreating My Room with Three.js Using React Three Fiber, I brought my real room into the browser—turning physical objects into an interactive table of contents, and using spatial memory to tell the story of my life and work over the past few years.
- Things to Keep in Mind When Using Images in Frontend Development Expanding on Jake Archibald's article, this post organizes how modern responsive images should be written: why width/height are still necessary, when to use CSS aspect-ratio, how to choose between AVIF and WebP, and using picture/source/srcset for art direction on mobile devices.
- CSS field-sizing — Auto-resize Form Elements with a Single Line of CSS Previously, auto-resizing a textarea required listening to scrollHeight in JavaScript. With CSS field-sizing: content, a single line replaces it all, supporting textarea, input, and select. This article covers the pain points of older approaches and how to use field-sizing.
- Make Your Link Underlines Look Better: text-underline-offset By default, underlines sit very close to the text. Some designers dislike this look, and personally, I don't think it looks great either.