Skip to content

Commit 1a81c41

Browse files
authored
Merge pull request #2216 from reduxjs/migrate-to-react-19
Migrate to React 19 (take 2)
2 parents 4a9ba60 + c58e397 commit 1a81c41

13 files changed

+384
-350
lines changed

.github/workflows/test.yml

+35-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
path: ./package.tgz
3939

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

4343
needs: [build]
4444
runs-on: ubuntu-latest
@@ -47,6 +47,19 @@ jobs:
4747
matrix:
4848
node: ['20.x']
4949
ts: ['4.7', '4.8', '4.9', '5.0', '5.1', '5.2', '5.3', '5.4', '5.5']
50+
react:
51+
[
52+
{
53+
version: '^18',
54+
types: ^18,
55+
react-dom: { version: '^18', types: '^18' },
56+
},
57+
{
58+
version: '^19',
59+
types: '^19',
60+
react-dom: { version: '^19', types: '^19' },
61+
},
62+
]
5063

5164
steps:
5265
- name: Checkout repo
@@ -67,6 +80,9 @@ jobs:
6780
- name: Install deps
6881
run: yarn install
6982

83+
- name: Install React ${{ matrix.react.version }} and React-DOM ${{ matrix.react.react-dom.version }}
84+
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 }}
85+
7086
- name: Install TypeScript ${{ matrix.ts }}
7187
run: yarn add typescript@${{ matrix.ts }}
7288

@@ -230,13 +246,27 @@ jobs:
230246
run: yarn build
231247

232248
test-dist:
233-
name: Run local tests against build artifact
249+
name: Run local tests against build artifact (React ${{ matrix.react.version }})
234250
needs: [build]
235251
runs-on: ubuntu-latest
236252
strategy:
237253
fail-fast: false
238254
matrix:
239255
node: ['20.x']
256+
react:
257+
[
258+
{
259+
version: '^18',
260+
types: ^18,
261+
react-dom: { version: '^18', types: '^18' },
262+
},
263+
{
264+
version: '^19',
265+
types: '^19',
266+
react-dom: { version: '^19', types: '^19' },
267+
},
268+
]
269+
240270
steps:
241271
- name: Checkout repo
242272
uses: actions/checkout@v4
@@ -259,6 +289,9 @@ jobs:
259289
- name: Check folder contents
260290
run: ls -lah
261291

292+
- name: Install React ${{ matrix.react.version }} and React-DOM ${{ matrix.react.react-dom.version }}
293+
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 }}
294+
262295
- name: Install build artifact
263296
run: yarn add ./package.tgz
264297

package.json

+11-12
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@
5151
"coverage": "codecov"
5252
},
5353
"peerDependencies": {
54-
"@types/react": "^18.2.25",
55-
"react": "^18.0",
54+
"@types/react": "^18.2.25 || ^19",
55+
"react": "^18.0 || ^19",
5656
"redux": "^5.0.0"
5757
},
5858
"peerDependenciesMeta": {
@@ -65,7 +65,7 @@
6565
},
6666
"dependencies": {
6767
"@types/use-sync-external-store": "^0.0.6",
68-
"use-sync-external-store": "^1.2.2"
68+
"use-sync-external-store": "^1.4.0"
6969
},
7070
"devDependencies": {
7171
"@babel/cli": "^7.24.7",
@@ -80,13 +80,13 @@
8080
"@babel/preset-typescript": "^7.24.7",
8181
"@microsoft/api-extractor": "^7.47.0",
8282
"@reduxjs/toolkit": "^2.2.5",
83-
"@testing-library/dom": "^10.1.0",
84-
"@testing-library/jest-dom": "^6.4.5",
85-
"@testing-library/react": "^16.0.0",
83+
"@testing-library/dom": "^10.4.0",
84+
"@testing-library/jest-dom": "^6.6.3",
85+
"@testing-library/react": "^16.1.0",
8686
"@types/node": "^20.14.2",
8787
"@types/prop-types": "^15.7.12",
88-
"@types/react": "18.3.3",
89-
"@types/react-dom": "^18.3.0",
88+
"@types/react": "^19.0.1",
89+
"@types/react-dom": "^19.0.1",
9090
"babel-eslint": "^10.1.0",
9191
"codecov": "^3.8.3",
9292
"cross-env": "^7.0.3",
@@ -96,11 +96,10 @@
9696
"eslint-plugin-import": "^2.29.1",
9797
"eslint-plugin-prettier": "^5.1.3",
9898
"eslint-plugin-react": "^7.34.2",
99-
"jsdom": "^24.1.0",
99+
"jsdom": "^25.0.1",
100100
"prettier": "^3.3.3",
101-
"react": "18.3.1",
102-
"react-dom": "18.3.1",
103-
"react-test-renderer": "18.3.1",
101+
"react": "^19.0.0",
102+
"react-dom": "^19.0.0",
104103
"redux": "^5.0.1",
105104
"rimraf": "^5.0.7",
106105
"tsup": "^8.3.5",

src/utils/hoistStatics.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Copied directly from:
22
// https://github.com/mridgway/hoist-non-react-statics/blob/main/src/index.js
3-
// https://unpkg.com/browse/@types/hoist-non-react-statics@3.3.1/index.d.ts
3+
// https://unpkg.com/browse/@types/hoist-non-react-statics@3.3.6/index.d.ts
44

55
/**
66
* Copyright 2015, Yahoo! Inc.
77
* Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
88
*/
9-
import type * as React from 'react'
9+
import type { ForwardRefExoticComponent, MemoExoticComponent } from 'react'
1010
import { ForwardRef, Memo, isMemo } from '../utils/react-is'
1111

1212
const REACT_STATICS = {
@@ -66,19 +66,19 @@ function getStatics(component: any) {
6666
}
6767

6868
export type NonReactStatics<
69-
S extends React.ComponentType<any>,
69+
Source,
7070
C extends {
7171
[key: string]: true
7272
} = {},
7373
> = {
7474
[key in Exclude<
75-
keyof S,
76-
S extends React.MemoExoticComponent<any>
75+
keyof Source,
76+
Source extends MemoExoticComponent<any>
7777
? keyof typeof MEMO_STATICS | keyof C
78-
: S extends React.ForwardRefExoticComponent<any>
78+
: Source extends ForwardRefExoticComponent<any>
7979
? keyof typeof FORWARD_REF_STATICS | keyof C
8080
: keyof typeof REACT_STATICS | keyof typeof KNOWN_STATICS | keyof C
81-
>]: S[key]
81+
>]: Source[key]
8282
}
8383

8484
const defineProperty = Object.defineProperty
@@ -89,12 +89,15 @@ const getPrototypeOf = Object.getPrototypeOf
8989
const objectPrototype = Object.prototype
9090

9191
export default function hoistNonReactStatics<
92-
T extends React.ComponentType<any>,
93-
S extends React.ComponentType<any>,
94-
C extends {
92+
Target,
93+
Source,
94+
CustomStatic extends {
9595
[key: string]: true
9696
} = {},
97-
>(targetComponent: T, sourceComponent: S): T & NonReactStatics<S, C> {
97+
>(
98+
targetComponent: Target,
99+
sourceComponent: Source,
100+
): Target & NonReactStatics<Source, CustomStatic> {
98101
if (typeof sourceComponent !== 'string') {
99102
// don't hoist over string (html) components
100103

src/utils/react-is.ts

+35-57
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import type { ElementType, MemoExoticComponent, ReactElement } from 'react'
2+
import * as React from 'react'
23

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

8-
const REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for('react.element')
9+
export const IS_REACT_19 = /* @__PURE__ */ React.version.startsWith('19')
10+
11+
const REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for(
12+
IS_REACT_19 ? 'react.transitional.element' : 'react.element',
13+
)
914
const REACT_PORTAL_TYPE = /* @__PURE__ */ Symbol.for('react.portal')
1015
const REACT_FRAGMENT_TYPE = /* @__PURE__ */ Symbol.for('react.fragment')
1116
const REACT_STRICT_MODE_TYPE = /* @__PURE__ */ Symbol.for('react.strict_mode')
1217
const REACT_PROFILER_TYPE = /* @__PURE__ */ Symbol.for('react.profiler')
13-
const REACT_PROVIDER_TYPE = /* @__PURE__ */ Symbol.for('react.provider')
18+
const REACT_CONSUMER_TYPE = /* @__PURE__ */ Symbol.for('react.consumer')
1419
const REACT_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for('react.context')
15-
const REACT_SERVER_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for(
16-
'react.server_context',
17-
)
1820
const REACT_FORWARD_REF_TYPE = /* @__PURE__ */ Symbol.for('react.forward_ref')
1921
const REACT_SUSPENSE_TYPE = /* @__PURE__ */ Symbol.for('react.suspense')
2022
const REACT_SUSPENSE_LIST_TYPE = /* @__PURE__ */ Symbol.for(
@@ -31,87 +33,63 @@ export const ForwardRef = REACT_FORWARD_REF_TYPE
3133
export const Memo = REACT_MEMO_TYPE
3234

3335
export function isValidElementType(type: any): type is ElementType {
34-
if (typeof type === 'string' || typeof type === 'function') {
35-
return true
36-
} // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill).
37-
38-
if (
36+
return typeof type === 'string' ||
37+
typeof type === 'function' ||
3938
type === REACT_FRAGMENT_TYPE ||
4039
type === REACT_PROFILER_TYPE ||
4140
type === REACT_STRICT_MODE_TYPE ||
4241
type === REACT_SUSPENSE_TYPE ||
4342
type === REACT_SUSPENSE_LIST_TYPE ||
44-
type === REACT_OFFSCREEN_TYPE
45-
) {
46-
return true
47-
}
48-
49-
if (typeof type === 'object' && type !== null) {
50-
if (
51-
type.$$typeof === REACT_LAZY_TYPE ||
52-
type.$$typeof === REACT_MEMO_TYPE ||
53-
type.$$typeof === REACT_PROVIDER_TYPE ||
54-
type.$$typeof === REACT_CONTEXT_TYPE ||
55-
type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
56-
// types supported by any Flight configuration anywhere since
57-
// we don't know which Flight build this will end up being used
58-
// with.
59-
type.$$typeof === REACT_CLIENT_REFERENCE ||
60-
type.getModuleId !== undefined
61-
) {
62-
return true
63-
}
64-
}
65-
66-
return false
43+
type === REACT_OFFSCREEN_TYPE ||
44+
(typeof type === 'object' &&
45+
type !== null &&
46+
(type.$$typeof === REACT_LAZY_TYPE ||
47+
type.$$typeof === REACT_MEMO_TYPE ||
48+
type.$$typeof === REACT_CONTEXT_TYPE ||
49+
type.$$typeof === REACT_CONSUMER_TYPE ||
50+
type.$$typeof === REACT_FORWARD_REF_TYPE ||
51+
type.$$typeof === REACT_CLIENT_REFERENCE ||
52+
type.getModuleId !== undefined))
53+
? !0
54+
: !1
6755
}
6856

6957
function typeOf(object: any): symbol | undefined {
7058
if (typeof object === 'object' && object !== null) {
71-
const $$typeof = object.$$typeof
59+
const { $$typeof } = object
7260

7361
switch ($$typeof) {
74-
case REACT_ELEMENT_TYPE: {
75-
const type = object.type
76-
77-
switch (type) {
62+
case REACT_ELEMENT_TYPE:
63+
switch (((object = object.type), object)) {
7864
case REACT_FRAGMENT_TYPE:
7965
case REACT_PROFILER_TYPE:
8066
case REACT_STRICT_MODE_TYPE:
8167
case REACT_SUSPENSE_TYPE:
8268
case REACT_SUSPENSE_LIST_TYPE:
83-
return type
84-
85-
default: {
86-
const $$typeofType = type && type.$$typeof
87-
88-
switch ($$typeofType) {
89-
case REACT_SERVER_CONTEXT_TYPE:
69+
return object
70+
default:
71+
switch (((object = object && object.$$typeof), object)) {
9072
case REACT_CONTEXT_TYPE:
9173
case REACT_FORWARD_REF_TYPE:
9274
case REACT_LAZY_TYPE:
9375
case REACT_MEMO_TYPE:
94-
case REACT_PROVIDER_TYPE:
95-
return $$typeofType
96-
76+
return object
77+
case REACT_CONSUMER_TYPE:
78+
return object
9779
default:
9880
return $$typeof
9981
}
100-
}
10182
}
102-
}
103-
104-
case REACT_PORTAL_TYPE: {
83+
case REACT_PORTAL_TYPE:
10584
return $$typeof
106-
}
10785
}
10886
}
109-
110-
return undefined
11187
}
11288

11389
export function isContextConsumer(object: any): object is ReactElement {
114-
return typeOf(object) === REACT_CONTEXT_TYPE
90+
return IS_REACT_19
91+
? typeOf(object) === REACT_CONSUMER_TYPE
92+
: typeOf(object) === REACT_CONTEXT_TYPE
11593
}
11694

11795
export function isMemo(object: any): object is MemoExoticComponent<any> {

0 commit comments

Comments
 (0)