React 17 and What is New JSX?

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

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

What is event delegation:

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>

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) {
// is the clicked element!
// If it was a list item
if( && == “LI”) {
// List item found! Output the ID!
console.log(“List item “,“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

Benefits of Event Delegation:

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


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.ceateElement(). 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:

  • 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};



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.


# 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

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:

  • 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 => ({
// This crashes in React 16 and earlier:

  • 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(() => {
return () => {


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

  • 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 />;


Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store