24/05/2021
What’s New in React v17?
Changes to Event Delegation
“In React 17, React will no longer attach event handlers at the document level. Instead, it will attach them to the root DOM container into which your React tree is rendered.” — React’s blog
Let’s have a look at an example. Consider attaching an onClick event to a button in React, like this:
The vanilla JS equivalent to the code above on compiling will look like this:
myButton.addEventListener('click', handleClick);
React then attaches one handler per event type to the document node directly, as opposed to attaching it to the DOM nodes on which they were declared. This is called event delegation.
Diagram displaying the differences between React 16 and 17.
Photo from React’s blog.
In React v17, the event handlers will no longer be attached at the document level, but they will be attached to the DOM container where the tree was rendered.
const rootNode = document.getElementById('root');
ReactDOM.render(, rootNode);
With this change in React v17, it is now safe to nest apps built with different versions of React.
New JSX Transform
React v17 offers a new, rewritten version of JSX Transform, though upgrading to this new transform is completely optional. It still offers a few benefits like:
You don’t need to import React.
Improves your bundle size.
If you wish to read more about this new transform, take a look at this blog post by the React team:
Introducing the New JSX Transform - React Blog
Although React 17 doesn't contain new features, it will provide support for a new version of the JSX transform. In this…
reactjs.org
Breaking Changes
There are a few breaking changes that are shipped with this new version.
1. Event delegation
As mentioned above, you might face some issues after upgrading.
In this new version, the event.stopPropagation() will actually stop your document handler from releasing:
document.addEventListener('click', function() {
// This custom handler will no longer receive clicks
// from React components that called e.stopPropagation()
});
To fix this, convert your event listener to use a capture phase by passing a { capture: true } option as the third argument, like so:
document.addEventListener('click', function() {
// Now this event handler uses the capture phase,
// so it receives *all* click events below!
}, { capture: true });
With this, we see that event delegation is now closer to the normal DOM than ever before.
2. Aligning with browsers
React has made a few changes related to the event system:
onScroll event no longer bubbles.
onBlur and onFocus events have now switched to using native focusin and focusout events internally, better matching React’s existing behavior and even providing more information.
onClickCapture now makes use of actual browser capture phase listeners.
3. No event pooling
From this new version, event pooling optimization has been removed from React due to continued confusion and because it doesn’t improve the performance.
In React 17, this code works as you would expect. The old event pooling optimization has been fully removed, so you can read the event fields whenever you need them.
4. Effect cleanup timing
This new version also makes the useEffect hook cleanup function timing more consistent.
useEffect(() => {
// This is the effect itself.
return () => { // This is its cleanup. };});
In React v16, the effect cleanup function is run synchronously. Per the React blog, “In React v17, the effect cleanup function always runs asynchronously — for example, if the component is unmounting, the cleanup runs after the screen has been updated.”
5. Consistent errors for returning undefined
In React v16, functions that return undefined always throw an error mostly because of how easy it is to return undefined unintentionally:
function Button() {
// We forgot to write return, so this component returns undefined.
// React surfaces this as an error instead of ignoring it.
;
}
Initially, this behavior was only exclusive to class and function components, but with this new version, forwardRef and memo components have now been added, making their behavior consistent with regular class and function components. Note that for instances in which you would render nothing intentionally, null will be returned instead.