Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes a bug that caused React 19 applications to fail during sku build #1143

Merged
merged 1 commit into from
Jan 17, 2025

Conversation

askoufis
Copy link
Contributor

@askoufis askoufis commented Jan 16, 2025

TL;DR: Babel's JSX runtime is incompatible with React 19. We've been using Babel's JSX runtime since before React had one. React's runtime effectively makes the Babel one obsolete, so the plugin that injects it has been removed in favour of letting @babel/preset-react inject React's runtime.

Caution

This PR does not explicitly implement support for React 19 or any of its features (e.g. RSC, the React compiler).
While your application may work with React 19, it is not recommended to upgrade just yet.

Problem

Applications that attempt to run a sku build with React 19 as a dependency will run into the following error during the static render:

Objects are not valid as a React child (found: object with keys {$$typeof, type, key, ref, props, _owner}). If you meant to render a collection of children, use an array instead.

This error occurs regardless of what is being rendered. My minimal reproduction involved rendering a single <div />.

Investigation

There were a number of stepping stones that lead me to find the cause of this problem. The first notable observation was that this error occurred during sku build, not during sku start. So something about the production environment and/or the lack of a dev server was the cause.

After some googling, I also found facebook/react#31832, which suggested that the issue was related to the JSX runtime somehow. Additionally, replacing the JSX <div /> with a createElement('div') in the static render resulted in a successful build. However, serving the production app with sku serve would result in Minified React error #525:

A React Element from an older version of React was rendered. This is not supported. It can happen if:
- Multiple copies of the "react" package is used.
- A library pre-bundled an old copy of "react" or "react/jsx-runtime".
- A compiler tries to "inline" JSX instead of using the runtime.

This adds to the theory that the JSX runtime is somehow to blame.

After this I wanted to inspect the actual code being used for the static render, so I looked at the dist/render.js file, but there weren't any notable differences in the code between react 18 and react 19.

Using RSDoctor to see the transform being applied by babel-loader, I noticed that the JSX runtime being injected into our code was actually babel's JSX runtime from @babel/runtime/helpers/jsx rather than the React one from react/jsx-runtime. This was being injected by @babel/plugin-transform-react-inline-elements which notably only runs in production. Removing this plugin and instead relying on the react JSX runtime (injected by @babel/preset-react) fixed the error.

Cause

It's not immediately apparent why swapping from babel's JSX runtime to React's would fix the issue. Comparing the result of the JSX runtime on a simple <div /> makes the issue more apparent:

// React 18
{
  '$$typeof': Symbol(react.element),
  type: 'div',
  key: null,
  ref: null,
  props: {},
  _owner: null
}

// React 19
{
  '$$typeof': Symbol(react.transitional.element),
  type: 'div',
  key: null,
  ref: null,
  props: {}
}

With a bit of digging, we can see that the symbol used to identify React elements was intentionally changed in React 19 to help make runtime mismatch errors more apparent. In reality the error we were seeing wasn't very helpful at all 😢.

Fix

The JSX runtime provided by @babel/plugin-transform-react-inline-elements
evidently doesn't support React 19. Removing it results in babel injecting React's runtime (via @babel/preset-react) which supports React 19, and fixes the build error.

We've likely just kept the babel runtime around because we were using it since well before React provided it's own JSX runtime. However, it's effectively obsolete thesedays, so I have no qualms with removing it.

Note

While I wanted to update a single fixture to use React 19 in order to reproduce the bug, due to various dependencies still not support React 19, dependency overrides would've been required to get this working.
I'd prefer to avoid overriding react dependencies, at least until we officially support React 19, so for that reason I chose to just implement the fix.

@askoufis askoufis requested a review from a team as a code owner January 16, 2025 03:49
Copy link

changeset-bot bot commented Jan 16, 2025

🦋 Changeset detected

Latest commit: 6795253

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
sku Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

@DanDroryAu DanDroryAu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find!

@askoufis askoufis merged commit 8de3fd2 into master Jan 17, 2025
5 checks passed
@askoufis askoufis deleted the fix-react-19-bug branch January 17, 2025 00:57
@seek-oss-ci seek-oss-ci mentioned this pull request Jan 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants