Skip to content

Commit

Permalink
Merge pull request #2216 from reduxjs/migrate-to-react-19
Browse files Browse the repository at this point in the history
Migrate to React 19 (take 2)
  • Loading branch information
markerikson authored Dec 10, 2024
2 parents 4a9ba60 + c58e397 commit 1a81c41
Show file tree
Hide file tree
Showing 13 changed files with 384 additions and 350 deletions.
37 changes: 35 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,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 @@ -47,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: '^19',
types: '^19',
react-dom: { version: '^19', types: '^19' },
},
]

steps:
- name: Checkout repo
Expand All @@ -67,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 @@ -230,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: '^19',
types: '^19',
react-dom: { version: '^19', types: '^19' },
},
]

steps:
- name: Checkout repo
uses: actions/checkout@v4
Expand All @@ -259,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
23 changes: 11 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,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 @@ -65,7 +65,7 @@
},
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.2.2"
"use-sync-external-store": "^1.4.0"
},
"devDependencies": {
"@babel/cli": "^7.24.7",
Expand All @@ -80,13 +80,13 @@
"@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",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@types/node": "^20.14.2",
"@types/prop-types": "^15.7.12",
"@types/react": "18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.1",
"babel-eslint": "^10.1.0",
"codecov": "^3.8.3",
"cross-env": "^7.0.3",
Expand All @@ -96,11 +96,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.1",
"prettier": "^3.3.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-test-renderer": "18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"redux": "^5.0.1",
"rimraf": "^5.0.7",
"tsup": "^8.3.5",
Expand Down
25 changes: 14 additions & 11 deletions src/utils/hoistStatics.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Copied directly from:
// https://github.com/mridgway/hoist-non-react-statics/blob/main/src/index.js
// https://unpkg.com/browse/@types/[email protected].1/index.d.ts
// https://unpkg.com/browse/@types/[email protected].6/index.d.ts

/**
* 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 { ForwardRefExoticComponent, MemoExoticComponent } from 'react'
import { ForwardRef, Memo, isMemo } from '../utils/react-is'

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

export type NonReactStatics<
S extends React.ComponentType<any>,
Source,
C extends {
[key: string]: true
} = {},
> = {
[key in Exclude<
keyof S,
S extends React.MemoExoticComponent<any>
keyof Source,
Source extends MemoExoticComponent<any>
? keyof typeof MEMO_STATICS | keyof C
: S extends React.ForwardRefExoticComponent<any>
: Source extends ForwardRefExoticComponent<any>
? keyof typeof FORWARD_REF_STATICS | keyof C
: keyof typeof REACT_STATICS | keyof typeof KNOWN_STATICS | keyof C
>]: S[key]
>]: Source[key]
}

const defineProperty = Object.defineProperty
Expand All @@ -89,12 +89,15 @@ const getPrototypeOf = Object.getPrototypeOf
const objectPrototype = Object.prototype

export default function hoistNonReactStatics<
T extends React.ComponentType<any>,
S extends React.ComponentType<any>,
C extends {
Target,
Source,
CustomStatic extends {
[key: string]: true
} = {},
>(targetComponent: T, sourceComponent: S): T & NonReactStatics<S, C> {
>(
targetComponent: Target,
sourceComponent: Source,
): Target & NonReactStatics<Source, CustomStatic> {
if (typeof sourceComponent !== 'string') {
// don't hoist over string (html) components

Expand Down
92 changes: 35 additions & 57 deletions src/utils/react-is.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
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/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 = /* @__PURE__ */ Symbol.for('react.element')
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_PROVIDER_TYPE = /* @__PURE__ */ Symbol.for('react.provider')
const REACT_CONSUMER_TYPE = /* @__PURE__ */ Symbol.for('react.consumer')
const REACT_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for('react.context')
const REACT_SERVER_CONTEXT_TYPE = /* @__PURE__ */ Symbol.for(
'react.server_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(
Expand All @@ -31,87 +33,63 @@ 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

0 comments on commit 1a81c41

Please sign in to comment.