React 17 and What is New JSX?

Devinder Suthwal
6 min readFeb 14, 2021

React 17 was released on 20th October, 2020. It doesn’t contain any developer facing new features. But there are lots of internal things that have changed in React. Below are the highlight of the main changes:

  1. Gradually Upgrade.
  2. Changes to Event Delegation
  3. New JSX
  4. Other Changes

Gradually Upgrade

React 17 allows embedding the tree that is managed by one version of React inside the tree that is managed by another version of React.

Embedding tree managed by React 17 in React 18.

This means that when React 18 and the next future versions come out, we’ll have the option to gradually upgrade the app.

Upgrading the full application at once is still the best solution. Loading two versions of React — even if one of them is loaded lazily on demand — is still not ideal. However, for larger apps that aren’t actively maintained, this option may make sense to consider, and React 17 enables those apps to not get left behind.

Changes to Event Delegation

First we need to know what is Event Delegation.

What is event delegation:

Event delegation allows you to avoid adding event listeners to specific nodes; instead, the event listener is added to one parent. That event listener analyzes bubbled events to find a match on child elements.

For example to a list as below, we can attach eventListener to <ul> only rather than to each <li>.

<ul id=”parent-list”>
<li id=”post-1">Item 1</li>
<li id=”post-2">Item 2</li>
<li id=”post-3">Item 3</li>
<li id=”post-4">Item 4</li>
<li id=”post-5">Item 5</li>
<li id=”post-6">Item 6</li>
</ul>

Rather than attaching eventListener to each element, we can attach the events to the parent.

// Get the element, add a click listener…
document.getElementById(“parent-list”).addEventListener(“click”, function(e) {
// e.target is the clicked element!
// If it was a list item
if(e.target && e.target.nodeName == “LI”) {
// List item found! Output the ID!
console.log(“List item “, e.target.id.replace(“post-”, “”), “ was clicked!”);
}
});

In React 17, React will no longer attach event handlers at the document level under the hood. Instead, it will attach them to the root DOM container into which your React tree is rendered:

This is direct image from reactjs.org

Benefits of Event Delegation:

  • It provides better performance.
  • It’s easier to add features like “Event Replaying” . Event Replaying helps in React Progressive Hydration in SSR.

New JSX

What is JSX? JSX is HTML like syntax introduced by React. Browsers cannot understand it directly, so this needs to be converted to plain JavaScript.

With the New JSX, React has shifted towards more static processing rather than dynamic.. Static processing is faster than dynamic processing.

To achieve this react has made few changes in syntax too.

Till React 16.x JSX internally transforms the HTML look like structure to React.createElement(). As Below

import React from ‘react’;

function App() {
return <h1>Hello World</h1>;
}

Will be converted to plain JavaScript code using JSX Transformer.

import React from ‘react’;

function App() {
return React.createElement(‘h1’, null, ‘Hello world’); //dynamic rendering
}

Here React.createElement() is proof that we need to import React and every node is a dynamic element.

From React 17 onward JSX transformation is done with the help of Transpilers like Babel and TypeScript.

function App() {
return <h1>Hello World</h1>;
}

Converted to plain JavaScript using compilers like babel, typeScript.

// Inserted by a compiler (don’t import it yourself!)
import {jsx as _jsx} from ‘react/jsx-runtime’;

function App() {
return _jsx(‘h1’, { children: ‘Hello world’ }); // Static rendering.
}

‘react/jsx-runtime’ is to provide plugins to transpilers. This is more optimized and has mostly static operations then dynamic operation. Dynamic operations are smart, but they are slower than static operations.

‘react/jsx-runtime’ has jsx-runtime(for prod env) and jsx-dev-runtime(for dev env) modules for JSX babel plugin and JSX TypeScript plugin. These modules are temporary and will be removed by react later.

React has worked with different teams to make JSX transformation work without pointing to React. Babel, TypeScript , Create React App, Next.js, Gatsby tools have been updated for new JSX. You can visit here to see their supporting versions.

Benefits of New JSX:

  • New JSX doesn’t require React to import. It doesn’t mean that React is no longer required for JSX. ‘react/jsx-runtime’ is needed to provide plugins to transpilers.
  • defaultProps on function components are no longer required. We can make use of JS default arguments. This reminds me of the depreciation of componentWillMount() because of the constructor().

class Foo {
static defaultProps = {foo: 1};

}

To

function Foo({foo = 1}) {

}

  • As there is one less line in each file, so bundle size also improved.
  • As there are more static operations than dynamic operations, it has better performance.

Installation

# for npm users

npm update @babel/core @babel/plugin-transform-react-jsx

# for yarn users

yarn upgrade @babel/core @babel/plugin-transform-react-jsx

If you are using @babel/preset-react:

# for npm users
npm update @babel/core @babel/preset-react
# for yarn users
yarn upgrade @babel/core @babel/preset-react

Currently, the old transform {“runtime”: “classic”} is the default option. To enable the new transform, you can pass {“runtime”: “automatic”} as an option to @babel/plugin-transform-react-jsx or @babel/preset-react:

// If you are using @babel/preset-react
{
“presets”: [
[“@babel/preset-react”, {
“runtime”: “automatic”
}]
]
}

Starting from Babel 8, “automatic” will be the default runtime for both plugins.

Similarly we need to make settings for TypeScript, Flow, ESLint, etc. to make it run.

Removing Unused React Imports

As with the new JSX, we need not to import React. So if we upgrade an existing project to React 17, we have to remove import React from ‘react’; from every file. This will reduce the bundle size.

React already took care of it and provided easy means for it.

cd your_project
npx react-codemod update-react-imports

Codemode(Code Modifications): Tool assisted code modifications can help evolve complex systems incrementally and aid in maintaining the health of large codebases.

Running react-codemode will:

  • Remove import React from ‘react’; from all functional components.
  • If any hook( like React.useState) is used in a component, the import will be modified to import { useState } from “react”) .

Other Changes:

  • Aligning with Browsers :These changes align React closer with the browser behavior and improve interoperability.
  • The onScroll event no longer bubbles to prevent common confusion.
  • React onFocus and onBlur events have switched to using the native focusin and focusout events under the hood, which more closely match React’s existing behavior and sometimes provide extra information.
  • Capture phase events (e.g. onClickCapture) now use real browser capture phase listeners.
  • No Event Pooling: React 17 removes the “event pooling” optimization from React. It doesn’t improve performance in modern browsers and confuses even experienced React users. In functional components this is no longer needed.

function handleChange(e) {
setData(data => ({
…data,
// This crashes in React 16 and earlier:
text: e.target.value
}));
}

  • Effect Cleanup Timing: In React 17, the effect cleanup function always runs asynchronously — for example, if the component is unmounting, the cleanup runs after the screen has been updated.(For the rare cases where you need an effect to block paint, e.g. to measure and position a tooltip, prefer useLayoutEffect.)

useEffect(() => {
someRef.current.someSetupMethod();
return () => {
someRef.current.someCleanupMethod();
};
});

To

useEffect(() => {
const instance = someRef.current;
instance.someSetupMethod();
return () => {
instance.someCleanupMethod();
};
});

  • Consistent Errors for Returning Undefined: In React 17, the behavior for forwardRef and memo components is consistent with regular function and class components. Returning undefined from them is an error.

let Button = forwardRef(() => {
// We forgot to write return, so this component returns undefined.
// React 17 surfaces this as an error instead of ignoring it.
<button />;
});

let Button = memo(() => {
// We forgot to write return, so this component returns undefined.
// React 17 surfaces this as an error instead of ignoring it.
<button />;
});

Conclusion:

Though there are no developer facing new feature in React 17. But there are very interesting conceptual changes here. Site performance is very hot topic currently. I think Event Delegation and New JSX are going to play vital role in site performance. As we know that static process are faster than dynamic process, we may see more steps in this direction in upcoming releases.

--

--