-
Notifications
You must be signed in to change notification settings - Fork 10.3k
feat: support React 19 #39306
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
base: master
Are you sure you want to change the base?
feat: support React 19 #39306
Conversation
d52b12d to
24546b0
Compare
6ab44fb to
6680733
Compare
3b417a1 to
ccc2189
Compare
7f66511 to
3797d4d
Compare
1131ffe to
e8b11fd
Compare
We support 20 and 22 now and this fail has been really annoying for me. I can't imagine it's providing much value.
It was actually navigating to the 404 page, which happened to meet the expected conditions.
`title()` immediately resolves with the current title, whereas `.get()` is deferred until the target element exists in the DOM: https://docs.cypress.io/app/core-concepts/introduction-to-cypress#Implicit-Assertions.
There is an undocumented change in behaviour in React 19: facebook/react#31660. Basically, in previous versions, an unchanged `dangerouslySetInnerHTML.__html` would not result in a re-render, but in React 19 referential equality of the `dangerouslySetInnerHTML` object is used instead. This code fundamentally depends on the previous behaviour, so that our own `innerHTML` updates do not get clobbered by a reset to this `dangerouslySetInnerHTML` placeholer value. As a workaround, this commit memoizes the object with `useMemo()`. This is safe for React 18 as well.
e8b11fd to
85d9c8a
Compare
|
|
||
| ### Testing with different React versions | ||
|
|
||
| To test compatibility with different React versions, you can use the `upgrade-react.js` script to update the React version in a test directory, then run the tests: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This already existed. I just added some docs and started (re!)using it in CI.
| deduplication: `${path}/deduplication/`, | ||
| htmlAndBodyAttributes: `${path}/html-and-body-attributes/`, | ||
| headWithWrapRooElement: `${path}/head-with-wrap-root-element/`, | ||
| withoutHead: `${path}/without-head/`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed this test happened to work because the 404 page happened to meet the right conditions (no Head()!
| export default defineConfig({ | ||
| e2e: { | ||
| baseUrl: "http://localhost:8000", | ||
| supportFile: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh hey, this must be why the support file wasn't working 😱
| ## Test Cases | ||
|
|
||
| 1. **Development Server** | ||
|
|
||
| - Verifies that the development server starts with React 19 | ||
| - Tests React 19 state updates and hooks | ||
| - Tests error boundaries with React 19 | ||
|
|
||
| 2. **Production Build** | ||
| - Verifies that the production build completes successfully with React 19 | ||
| - Checks for the existence of expected output files | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| ## Test Cases | |
| 1. **Development Server** | |
| - Verifies that the development server starts with React 19 | |
| - Tests React 19 state updates and hooks | |
| - Tests error boundaries with React 19 | |
| 2. **Production Build** | |
| - Verifies that the production build completes successfully with React 19 | |
| - Checks for the existence of expected output files |
I believe this is because of a patch bump of `graphql`?
Summary
This adds support for React 19 to all Gatsby packages, while maintaining support for React 18.
This is not a breaking change.
All packages' peer dependencies on
reactandreact-domhave been extended from^18.0.0to^18.0.0 || ^19.0.0.All existing stable Gatsby functionality is intended to work with React 19.
Upgrade Guide
To upgrade to React 19, first upgrade
gatsbyand all yourgatsby-*dependencies to the latest version. Then, follow the React 19 upgrade guide. No other changes are required.Please note:
New features
Gatsby now supports React 19's new root error callbacks.
Users can export
onCaughtErrorandonUncaughtErrorfrom theirgatsby-browser.jsto handle errors caught by error boundaries and uncaught errors respectively:In development, these errors also appear in Gatsby's Fast Refresh error overlay. These callbacks are only invoked in React 19.
Implementation
This PR configures CI to run the existing
development-runtimeandproduction-runtimee2e test suites against both React 18 and 19.fix(gatsby-plugin-image): work around a regression in React 19 core
There is an undocumented change in behaviour in React 19: facebook/react#31660. Basically, in previous versions, an unchanged
dangerouslySetInnerHTML.__htmlwould not result in a re-render, but in React 19 referential equality of thedangerouslySetInnerHTMLobject is used instead.The
gatsby-plugin-imageimplementation fundamentally depends on the previous behaviour, so that our owninnerHTMLupdates do not get clobbered by a reset to thisdangerouslySetInnerHTMLplaceholer value.As a workaround, this commit memoizes the object with
useMemo().This is safe for React 18 as well.
fix: replace usages of
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED😶There was one use of
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?.ReactDebugCurrentFrame?.getCurrentStack(). React 19 introducedReact.captureOwnerStack()which we'll now use instead if available.fix: avoid conflicts between Gatsby Head API and new React 19 document metadata feature
There are two conflicts here:
<html>or<body>element anywhere in the tree.<title>,<meta>, etc.) automatically into the<head>, with similar semantics as Gatsby's Head API.These both conflict with Gatsby's Head API implementation, which renders all these elements in a hidden
<div>, which it later finds to extract, merge, and apply attributes on the actual DOM nodes.We work around this with two techniques:
<head>are wrapped in an<svg>tag 😬, which prevents React 19's new mechanism from hoisting them, letting Gatsby process them as before.React.createElement(when using React 19 only) to intercept<html>and<body>elements within the Gatsby Head API context, replacing them with<div data-original-tag="html|body">stand-ins. This allows Gatsby to extract attributes from these elements without triggering React 19's restrictions.Future work
<HtmlAttributes>and<BodyAttributes>components (or some other API) to allow phasing out the nonstandard<html>/<body>magic?<svg>workaround by augmenting theReact.createElementmonkey-patch to check for<title>,<meta>, etc.? One workaround is better than two, maybe.Experimental Partial Hydration incompatibility with React 19
Gatsby's experimental Partial Hydration feature has been flagged as experimental for about three years and relies on React's experimental
react-server-dom-webpackpackage APIs that were substantially overhauled between React 18 and 19 as part of the RSC stabilization effort. Gatsby has been pinned to version0.0.0-experimental-c8b778b7f-20220825for years. The feature used experimental RSC APIs that no longer exist or are incompatible with React 19 for various reasons. Porting to React 19's stabilized RSC architecture would require substantial effort and the feature has seen limited adoption. It took massive research and iteration for other frameworks to reach RSC maturity and much of the effort was in underlying bundlers. It's very unlikely Gatsby will tackle this.The problematic import (
react-server-dom-webpack) was previously imported statically at the top of the module, causing React 19 projects to encounter import errors even when not using Partial Hydration. In this PR, we simply moved the import to a conditional async import that only loads when Partial Hydration is actually enabled via the (existing) opt-in flag.This allows the majority of Gatsby projects to safely upgrade to React 19 while allowing projects that have opted in to Partial Hydration to continue using it by remaining on React 18 while still being able to receive Gatsby upgrades.