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

Migrate to React 19 #2172

Draft
wants to merge 57 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
1af8999
Update `react` and `react-dom` to v19 beta
aryaemami59 May 10, 2024
f7bbf29
Set `@types/react` and `@types/react-dom` to temporary types packages
aryaemami59 May 10, 2024
bab94af
Run tests against different versions of React during CI
aryaemami59 May 10, 2024
51cb8a5
Remove the now deprecated `react-test-renderer` package
aryaemami59 May 10, 2024
5811eee
Bump `@testing-library/react` to version 15.0.7
aryaemami59 May 10, 2024
7288def
Replace the now removed `ReactDOM` methods
aryaemami59 May 10, 2024
df2a262
Replace `@testing-library/react-hooks` with `@testing-library/react`
aryaemami59 May 10, 2024
61b7741
Update `react-is` implementation
aryaemami59 May 10, 2024
21acad9
Export `IS_REACT_19` and re-use in tests
aryaemami59 May 10, 2024
8d97efe
Update `wrapper` `props` in `useDispatch.spec.tsx` to resolve type error
aryaemami59 May 10, 2024
98eb6b4
Remove unnecessary `rtl.cleanup` calls
aryaemami59 May 10, 2024
1dd0510
Add `@ts-ignore` for type issue related to `@types/react` ^18.61
aryaemami59 May 10, 2024
0f53b31
Uncomment type test
aryaemami59 May 10, 2024
bad8167
Add TODO comment about different rendering behaviors in React 18 vs 19
aryaemami59 May 10, 2024
bb116a6
Update `react` and `@types/react` in `peerDependencies`
aryaemami59 May 10, 2024
67e9e85
Update React
aryaemami59 May 14, 2024
544429b
Update React
aryaemami59 May 14, 2024
b13c201
Update `use-sync-external-store` to the new beta version
aryaemami59 May 14, 2024
dd32f4d
Use `types-use-sync-external-store` for `@types/use-sync-external-store`
aryaemami59 May 14, 2024
2a60e9f
Bump React to the new rc
aryaemami59 May 16, 2024
cdda866
Bump `use-sync-external-store` to the new rc
aryaemami59 May 16, 2024
7eae8e9
Test against rc version of React during CI
aryaemami59 May 16, 2024
e5e5a2c
Fix skipped tests in `ssr.spec.tsx`
aryaemami59 May 16, 2024
a45f441
Fix React 18 ssr test
aryaemami59 May 16, 2024
04a2b4e
Change `.toHaveBeenCalledTimes(0)` to `.not.toHaveBeenCalled()`
aryaemami59 May 16, 2024
1942a70
Change `.toHaveBeenCalledTimes(1)` to `.toHaveBeenCalledOnce()`
aryaemami59 May 16, 2024
27d843d
Update React version
aryaemami59 May 18, 2024
b739672
Update `use-sync-external-store`
aryaemami59 May 18, 2024
dbe5865
Fix duplicate `React` import in `hoistStatics.ts`
aryaemami59 May 19, 2024
b1567b1
Update React
aryaemami59 May 25, 2024
333b596
Bump `@types/react` and `@types/react-dom` to rc versions
aryaemami59 May 25, 2024
3fa9f4c
Update `use-sync-external-store`
aryaemami59 May 25, 2024
5932d9f
Bump `@types/use-sync-external-store` to rc version
aryaemami59 May 25, 2024
f5caae0
Update React and React-DOM
aryaemami59 Jun 1, 2024
a2baa72
Update `use-sync-external-store`
aryaemami59 Jun 1, 2024
d9b14e0
Add `@__PURE__` annotations to `react-is` symbols
aryaemami59 Jun 1, 2024
15ae7d6
Bump `@testing-library/react` and add `@testing-library/dom`
aryaemami59 Jun 3, 2024
ff55fc4
Update React and React-DOM
aryaemami59 Jun 3, 2024
c24d75f
Update `use-sync-external-store`
aryaemami59 Jun 3, 2024
bf0639f
Update the lockfile
aryaemami59 Jun 26, 2024
54a8b4a
Bump `@testing-library/dom` to version 10.2.0
aryaemami59 Jun 26, 2024
1895538
Bump `@testing-library/jest-dom` to version 6.4.6
aryaemami59 Jun 26, 2024
79e927f
Bump `@testing-library/dom` to version 10.3.1
aryaemami59 Jul 12, 2024
2b04227
Bump `@reduxjs/toolkit` to version 2.2.6
aryaemami59 Jul 12, 2024
576b14d
Bump `@testing-library/dom` to version 10.3.2
aryaemami59 Jul 20, 2024
feb965e
Bump `@testing-library/dom` to version 10.4.0
aryaemami59 Jul 23, 2024
983e156
Bump `@testing-library/jest-dom` to version 6.4.7
aryaemami59 Jul 23, 2024
befb280
Bump `@testing-library/jest-dom` to version 6.4.8
aryaemami59 Jul 25, 2024
f55c58f
Bump `jsdom` to version 24.1.1
aryaemami59 Jul 25, 2024
f4b2736
Bump `@reduxjs/toolkit` to version 2.2.7
aryaemami59 Aug 2, 2024
2e36be3
Update lockfile
aryaemami59 Aug 6, 2024
6e9a35e
Bump `jsdom` to version 25.0.0
aryaemami59 Aug 27, 2024
f11d486
Bump `@testing-library/jest-dom` to version 6.5.0
aryaemami59 Aug 27, 2024
0f52537
Update lockfile
aryaemami59 Aug 27, 2024
8767f04
Convert `default` import of React to a wildcard import
aryaemami59 Aug 27, 2024
a042c60
Do not run `coverage` during CI
aryaemami59 Oct 2, 2024
67a048a
Bump `@testing-library/react` to version 16.0.1
aryaemami59 Sep 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ jobs:
- name: Run test suite
run: yarn test

- name: Collect coverage
run: yarn coverage

- name: Build
run: yarn build

Expand All @@ -41,7 +38,7 @@ jobs:
path: ./package.tgz

test-types:
name: Test Types with TypeScript ${{ matrix.ts }}
name: Test Types with TypeScript ${{ matrix.ts }} and React ${{ matrix.react.version }}

needs: [build]
runs-on: ubuntu-latest
Expand All @@ -50,6 +47,19 @@ jobs:
matrix:
node: ['20.x']
ts: ['4.7', '4.8', '4.9', '5.0', '5.1', '5.2', '5.3', '5.4', '5.5']
react:
[
{
version: '^18',
types: ^18,
react-dom: { version: '^18', types: '^18' },
},
{
version: 'rc',
types: 'npm:types-react@rc',
react-dom: { version: 'rc', types: 'npm:types-react-dom@rc' },
},
]

steps:
- name: Checkout repo
Expand All @@ -70,6 +80,9 @@ jobs:
- name: Install deps
run: yarn install

- name: Install React ${{ matrix.react.version }} and React-DOM ${{ matrix.react.react-dom.version }}
run: yarn add -D react@${{ matrix.react.version }} react-dom@${{ matrix.react.react-dom.version }} @types/react@${{ matrix.react.types }} @types/react-dom@${{ matrix.react.react-dom.types }}

- name: Install TypeScript ${{ matrix.ts }}
run: yarn add typescript@${{ matrix.ts }}

Expand Down Expand Up @@ -233,13 +246,27 @@ jobs:
run: yarn build

test-dist:
name: Run local tests against build artifact
name: Run local tests against build artifact (React ${{ matrix.react.version }})
needs: [build]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: ['20.x']
react:
[
{
version: '^18',
types: ^18,
react-dom: { version: '^18', types: '^18' },
},
{
version: 'rc',
types: 'npm:types-react@rc',
react-dom: { version: 'rc', types: 'npm:types-react-dom@rc' },
},
]

steps:
- name: Checkout repo
uses: actions/checkout@v4
Expand All @@ -262,6 +289,9 @@ jobs:
- name: Check folder contents
run: ls -lah

- name: Install React ${{ matrix.react.version }} and React-DOM ${{ matrix.react.react-dom.version }}
run: yarn add -D react@${{ matrix.react.version }} react-dom@${{ matrix.react.react-dom.version }} @types/react@${{ matrix.react.types }} @types/react-dom@${{ matrix.react.react-dom.types }}

- name: Install build artifact
run: yarn add ./package.tgz

Expand Down
27 changes: 13 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
"coverage": "codecov"
},
"peerDependencies": {
"@types/react": "^18.2.25",
"react": "^18.0",
"@types/react": "^18.2.25 || ^19",
"react": "^18.0 || ^19",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
Expand All @@ -63,8 +63,8 @@
}
},
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.2.2"
"@types/use-sync-external-store": "npm:types-use-sync-external-store@rc",
"use-sync-external-store": "^1.0.0 || ^1.4.0-rc.0"
},
"devDependencies": {
"@babel/cli": "^7.24.7",
Expand All @@ -78,14 +78,14 @@
"@babel/preset-env": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@microsoft/api-extractor": "^7.47.0",
"@reduxjs/toolkit": "^2.2.5",
"@testing-library/dom": "^10.1.0",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^16.0.0",
"@reduxjs/toolkit": "^2.2.7",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@types/node": "^20.14.2",
"@types/prop-types": "^15.7.12",
"@types/react": "18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc",
"babel-eslint": "^10.1.0",
"codecov": "^3.8.3",
"cross-env": "^7.0.3",
Expand All @@ -95,11 +95,10 @@
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.2",
"jsdom": "^24.1.0",
"jsdom": "^25.0.0",
"prettier": "^3.3.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-test-renderer": "18.3.1",
"react": "19.0.0-rc.0",
"react-dom": "19.0.0-rc.0",
"redux": "^5.0.1",
"rimraf": "^5.0.7",
"tsup": "7.0.0",
Expand Down
16 changes: 10 additions & 6 deletions src/utils/hoistStatics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
* Copyright 2015, Yahoo! Inc.
* Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
import type * as React from 'react'
import type {
ComponentType,
ForwardRefExoticComponent,
MemoExoticComponent,
} from 'react'
import { ForwardRef, Memo, isMemo } from '../utils/react-is'

const REACT_STATICS = {
Expand Down Expand Up @@ -66,16 +70,16 @@ function getStatics(component: any) {
}

export type NonReactStatics<
S extends React.ComponentType<any>,
S extends ComponentType<any>,
C extends {
[key: string]: true
} = {},
> = {
[key in Exclude<
keyof S,
S extends React.MemoExoticComponent<any>
S extends MemoExoticComponent<any>
? keyof typeof MEMO_STATICS | keyof C
: S extends React.ForwardRefExoticComponent<any>
: S extends ForwardRefExoticComponent<any>
? keyof typeof FORWARD_REF_STATICS | keyof C
: keyof typeof REACT_STATICS | keyof typeof KNOWN_STATICS | keyof C
>]: S[key]
Expand All @@ -89,8 +93,8 @@ const getPrototypeOf = Object.getPrototypeOf
const objectPrototype = Object.prototype

export default function hoistNonReactStatics<
T extends React.ComponentType<any>,
S extends React.ComponentType<any>,
T extends ComponentType<any>,
S extends ComponentType<any>,
C extends {
[key: string]: true
} = {},
Expand Down
118 changes: 51 additions & 67 deletions src/utils/react-is.ts
Original file line number Diff line number Diff line change
@@ -1,111 +1,95 @@
import type { ElementType, MemoExoticComponent, ReactElement } from 'react'
import * as React from 'react'

// Directly ported from:
// https://unpkg.com/browse/react-is@18.3.0-canary-ee68446ff-20231115/cjs/react-is.production.js
// https://unpkg.com/browse/react-is@19.0.0-beta-04b058868c-20240508/cjs/react-is.production.js
// It's very possible this could change in the future, but given that
// we only use these in `connect`, this is a low priority.

const REACT_ELEMENT_TYPE = Symbol.for('react.element')
const REACT_PORTAL_TYPE = Symbol.for('react.portal')
const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment')
const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode')
const REACT_PROFILER_TYPE = Symbol.for('react.profiler')
const REACT_PROVIDER_TYPE = Symbol.for('react.provider')
const REACT_CONTEXT_TYPE = Symbol.for('react.context')
const REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context')
const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref')
const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense')
const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list')
const REACT_MEMO_TYPE = Symbol.for('react.memo')
const REACT_LAZY_TYPE = Symbol.for('react.lazy')
const REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen')
const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference')
export const IS_REACT_19 = /* @__PURE__ */ React.version.startsWith('19')

const REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for(
IS_REACT_19 ? 'react.transitional.element' : 'react.element',
)
const REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for('react.portal')
const REACT_FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for('react.fragment')
const REACT_STRICT_MODE_TYPE = /* @__PURE__ */ Symbol.for('react.strict_mode')
const REACT_PROFILER_TYPE = /* @__PURE__ */ Symbol.for('react.profiler')
const REACT_CONSUMER_TYPE = /* @__PURE__ */ Symbol.for('react.consumer')
const REACT_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for('react.context')
const REACT_FORWARD_REF_TYPE = /* @__PURE__ */ Symbol.for('react.forward_ref')
const REACT_SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for('react.suspense')
const REACT_SUSPENSE_LIST_TYPE = /* @__PURE__ */ Symbol.for(
'react.suspense_list',
)
const REACT_MEMO_TYPE = /* @__PURE__ */ Symbol.for('react.memo')
const REACT_LAZY_TYPE = /* @__PURE__ */ Symbol.for('react.lazy')
const REACT_OFFSCREEN_TYPE = /* @__PURE__ */ Symbol.for('react.offscreen')
const REACT_CLIENT_REFERENCE = /* @__PURE__ */ Symbol.for(
'react.client.reference',
)

export const ForwardRef = REACT_FORWARD_REF_TYPE
export const Memo = REACT_MEMO_TYPE

export function isValidElementType(type: any): type is ElementType {
if (typeof type === 'string' || typeof type === 'function') {
return true
} // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill).

if (
return typeof type === 'string' ||
typeof type === 'function' ||
type === REACT_FRAGMENT_TYPE ||
type === REACT_PROFILER_TYPE ||
type === REACT_STRICT_MODE_TYPE ||
type === REACT_SUSPENSE_TYPE ||
type === REACT_SUSPENSE_LIST_TYPE ||
type === REACT_OFFSCREEN_TYPE
) {
return true
}

if (typeof type === 'object' && type !== null) {
if (
type.$$typeof === REACT_LAZY_TYPE ||
type.$$typeof === REACT_MEMO_TYPE ||
type.$$typeof === REACT_PROVIDER_TYPE ||
type.$$typeof === REACT_CONTEXT_TYPE ||
type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
// we don't know which Flight build this will end up being used
// with.
type.$$typeof === REACT_CLIENT_REFERENCE ||
type.getModuleId !== undefined
) {
return true
}
}

return false
type === REACT_OFFSCREEN_TYPE ||
(typeof type === 'object' &&
type !== null &&
(type.$$typeof === REACT_LAZY_TYPE ||
type.$$typeof === REACT_MEMO_TYPE ||
type.$$typeof === REACT_CONTEXT_TYPE ||
type.$$typeof === REACT_CONSUMER_TYPE ||
type.$$typeof === REACT_FORWARD_REF_TYPE ||
type.$$typeof === REACT_CLIENT_REFERENCE ||
type.getModuleId !== undefined))
? !0
: !1
}

function typeOf(object: any): symbol | undefined {
if (typeof object === 'object' && object !== null) {
const $$typeof = object.$$typeof
const { $$typeof } = object

switch ($$typeof) {
case REACT_ELEMENT_TYPE: {
const type = object.type

switch (type) {
case REACT_ELEMENT_TYPE:
switch (((object = object.type), object)) {
case REACT_FRAGMENT_TYPE:
case REACT_PROFILER_TYPE:
case REACT_STRICT_MODE_TYPE:
case REACT_SUSPENSE_TYPE:
case REACT_SUSPENSE_LIST_TYPE:
return type

default: {
const $$typeofType = type && type.$$typeof

switch ($$typeofType) {
case REACT_SERVER_CONTEXT_TYPE:
return object
default:
switch (((object = object && object.$$typeof), object)) {
case REACT_CONTEXT_TYPE:
case REACT_FORWARD_REF_TYPE:
case REACT_LAZY_TYPE:
case REACT_MEMO_TYPE:
case REACT_PROVIDER_TYPE:
return $$typeofType

return object
case REACT_CONSUMER_TYPE:
return object
default:
return $$typeof
}
}
}
}

case REACT_PORTAL_TYPE: {
case REACT_PORTAL_TYPE:
return $$typeof
}
}
}

return undefined
}

export function isContextConsumer(object: any): object is ReactElement {
return typeOf(object) === REACT_CONTEXT_TYPE
return IS_REACT_19
? typeOf(object) === REACT_CONSUMER_TYPE
: typeOf(object) === REACT_CONTEXT_TYPE
}

export function isMemo(object: any): object is MemoExoticComponent<any> {
Expand Down
Loading