diff --git a/package.json b/package.json index 4533388f2..0d83863fc 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,9 @@ "import": "./dist/react-redux.alternate-renderers.mjs" } }, - "sideEffects": "false", + "sideEffects": false, "files": [ - "dist", - "es" + "dist" ], "scripts": { "build": "tsup", @@ -72,7 +71,6 @@ }, "dependencies": { "@types/use-sync-external-store": "^0.0.3", - "react-is": "^18.0.0", "use-sync-external-store": "^1.0.0" }, "devDependencies": { @@ -87,17 +85,15 @@ "@babel/preset-env": "^7.12.1", "@babel/preset-typescript": "^7.14.5", "@microsoft/api-extractor": "^7.18.1", - "@reduxjs/toolkit": "^2.0.0-beta.0", + "@reduxjs/toolkit": "^2.0.0-beta.4", "@testing-library/jest-dom": "^5.11.5", "@testing-library/jest-native": "^3.4.3", "@testing-library/react": "13.0.0", "@testing-library/react-12": "npm:@testing-library/react@^12", "@testing-library/react-hooks": "^3.4.2", "@testing-library/react-native": "^7.1.0", - "@types/object-assign": "^4.0.30", "@types/react": "^18", "@types/react-dom": "^18", - "@types/react-is": "^17", "@types/react-native": "^0.67.4", "@typescript-eslint/eslint-plugin": "^4.28.0", "@typescript-eslint/parser": "^4.28.0", diff --git a/src/components/connect.tsx b/src/components/connect.tsx index 2380df504..bcec8eec8 100644 --- a/src/components/connect.tsx +++ b/src/components/connect.tsx @@ -1,7 +1,7 @@ /* eslint-disable valid-jsdoc, @typescript-eslint/no-unused-vars */ import type { ComponentType } from 'react' import * as React from 'react' -import { isValidElementType, isContextConsumer } from 'react-is' +import { isValidElementType, isContextConsumer } from '../utils/react-is' import type { Store } from 'redux' @@ -491,15 +491,14 @@ function connect< type WrappedComponentProps = TProps & ConnectPropsMaybeWithoutContext - if ( - process.env.NODE_ENV !== 'production' && - !isValidElementType(WrappedComponent) - ) { - throw new Error( - `You must pass a component to the function returned by connect. Instead received ${stringifyComponent( - WrappedComponent - )}` - ) + if (process.env.NODE_ENV !== 'production') { + const isValid = /*#__PURE__*/ isValidElementType(WrappedComponent) + if (!isValid) + throw new Error( + `You must pass a component to the function returned by connect. Instead received ${stringifyComponent( + WrappedComponent + )}` + ) } const wrappedComponentName = @@ -544,12 +543,22 @@ function connect< const ContextToUse: ReactReduxContextInstance = React.useMemo(() => { // Users may optionally pass in a custom context instance to use instead of our ReactReduxContext. // Memoize the check that determines which context instance we should use. - return propsContext && - propsContext.Consumer && - // @ts-ignore - isContextConsumer() - ? propsContext - : Context + let ResultContext = Context + if (propsContext?.Consumer) { + if (process.env.NODE_ENV !== 'production') { + const isValid = /*#__PURE__*/ isContextConsumer( + // @ts-ignore + + ) + if (!isValid) { + throw new Error( + 'You must pass a valid React context consumer as `props.context`' + ) + } + ResultContext = propsContext + } + } + return ResultContext }, [propsContext, Context]) // Retrieve the store and ancestor subscription via context, if available @@ -797,10 +806,10 @@ function connect< const forwarded = _forwarded as ConnectedWrapperComponent forwarded.displayName = displayName forwarded.WrappedComponent = WrappedComponent - return hoistStatics(forwarded, WrappedComponent) + return /*#__PURE__*/ hoistStatics(forwarded, WrappedComponent) } - return hoistStatics(Connect, WrappedComponent) + return /*#__PURE__*/ hoistStatics(Connect, WrappedComponent) } return wrapWithConnect diff --git a/src/utils/hoistStatics.ts b/src/utils/hoistStatics.ts index 7a2d8b0a6..5e41e0185 100644 --- a/src/utils/hoistStatics.ts +++ b/src/utils/hoistStatics.ts @@ -7,7 +7,7 @@ * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ import type * as React from 'react' -import { ForwardRef, Memo, isMemo } from 'react-is' +import { ForwardRef, Memo, isMemo } from '../utils/react-is' const REACT_STATICS = { childContextTypes: true, diff --git a/src/utils/react-is.ts b/src/utils/react-is.ts new file mode 100644 index 000000000..300812089 --- /dev/null +++ b/src/utils/react-is.ts @@ -0,0 +1,113 @@ +import type { ElementType, MemoExoticComponent, ReactElement } from 'react' + +// Directly ported from: +// https://unpkg.com/browse/react-is@18.3.0-canary-ee68446ff-20231115/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 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 ( + 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 +} + +function typeOf(object: any): symbol | undefined { + if (typeof object === 'object' && object !== null) { + const $$typeof = object.$$typeof + + switch ($$typeof) { + case REACT_ELEMENT_TYPE: { + const type = object.type + + switch (type) { + 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: + case REACT_CONTEXT_TYPE: + case REACT_FORWARD_REF_TYPE: + case REACT_LAZY_TYPE: + case REACT_MEMO_TYPE: + case REACT_PROVIDER_TYPE: + return $$typeofType + + default: + return $$typeof + } + } + } + } + + case REACT_PORTAL_TYPE: { + return $$typeof + } + } + } + + return undefined +} + +export function isContextConsumer(object: any): object is ReactElement { + return typeOf(object) === REACT_CONTEXT_TYPE +} + +export function isMemo(object: any): object is MemoExoticComponent { + return typeOf(object) === REACT_MEMO_TYPE +} diff --git a/test/typetests/react-redux-types.typetest.tsx b/test/typetests/react-redux-types.typetest.tsx index eb1b510ef..18a1fc2d2 100644 --- a/test/typetests/react-redux-types.typetest.tsx +++ b/test/typetests/react-redux-types.typetest.tsx @@ -36,7 +36,7 @@ import { fetchCount, } from './counterApp' -import objectAssign from 'object-assign' +const objectAssign = Object.assign class Counter extends Component { render() { diff --git a/yarn.lock b/yarn.lock index 40ac343c7..bb23c4508 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2419,23 +2419,23 @@ __metadata: languageName: node linkType: hard -"@reduxjs/toolkit@npm:^2.0.0-beta.0": - version: 2.0.0-beta.0 - resolution: "@reduxjs/toolkit@npm:2.0.0-beta.0" +"@reduxjs/toolkit@npm:^2.0.0-beta.4": + version: 2.0.0-beta.4 + resolution: "@reduxjs/toolkit@npm:2.0.0-beta.4" dependencies: immer: ^10.0.2 - redux: 5.0.0-beta.0 - redux-thunk: 3.0.0-alpha.3 - reselect: ^5.0.0-alpha.2 + redux: ^5.0.0-beta.0 + redux-thunk: ^3.0.0-beta.0 + reselect: ^5.0.0-beta.0 peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 + react-redux: ^7.2.1 || ^8.0.2 || ^9.0.0-beta.0 peerDependenciesMeta: react: optional: true react-redux: optional: true - checksum: e03ecca1ef61d1073908095cb43215b3f8254c0a81872d44b0f49788ade72bf303bffd43dee57a8f8acebf76fade7e0bfb3d049189aed4467cb98b2e5e7c5b01 + checksum: f7fe690b26840485a0dbc4a367424fc6c96604d8f6cab17ccb216ce1320d9a5c2f81c13d4e93d14095de6b0196ac742bff43b4834494be258ee42559e9dd429c languageName: node linkType: hard @@ -2785,13 +2785,6 @@ __metadata: languageName: node linkType: hard -"@types/object-assign@npm:^4.0.30": - version: 4.0.30 - resolution: "@types/object-assign@npm:4.0.30" - checksum: 24e0471ddcd578b7ea72d5174e9cd6b68d78b5fa00f9f48cee38713c0e2886c6c3478c53c04d0508d16deb4370eed71ed0bb1f5b9aaa406e61f07ffed5da1d3b - languageName: node - linkType: hard - "@types/prettier@npm:^2.1.5": version: 2.7.3 resolution: "@types/prettier@npm:2.7.3" @@ -2815,15 +2808,6 @@ __metadata: languageName: node linkType: hard -"@types/react-is@npm:^17": - version: 17.0.3 - resolution: "@types/react-is@npm:17.0.3" - dependencies: - "@types/react": "*" - checksum: 6abb7c47d54f012272650df8a962a28bd82f219291e5ef8b4dfa7fe0bb98ae243b060bf9fbe8ceff6213141794855a006db194b490b00ffd15842ae19d0ce1f0 - languageName: node - linkType: hard - "@types/react-native@npm:^0.67.4": version: 0.67.4 resolution: "@types/react-native@npm:0.67.4" @@ -9381,17 +9365,15 @@ __metadata: "@babel/preset-env": ^7.12.1 "@babel/preset-typescript": ^7.14.5 "@microsoft/api-extractor": ^7.18.1 - "@reduxjs/toolkit": ^2.0.0-beta.0 + "@reduxjs/toolkit": ^2.0.0-beta.4 "@testing-library/jest-dom": ^5.11.5 "@testing-library/jest-native": ^3.4.3 "@testing-library/react": 13.0.0 "@testing-library/react-12": "npm:@testing-library/react@^12" "@testing-library/react-hooks": ^3.4.2 "@testing-library/react-native": ^7.1.0 - "@types/object-assign": ^4.0.30 "@types/react": ^18 "@types/react-dom": ^18 - "@types/react-is": ^17 "@types/react-native": ^0.67.4 "@types/use-sync-external-store": ^0.0.3 "@typescript-eslint/eslint-plugin": ^4.28.0 @@ -9412,7 +9394,6 @@ __metadata: prettier: ^2.1.2 react: 18.2.0 react-dom: 18.2.0 - react-is: ^18.0.0 react-native: ^0.71.11 react-test-renderer: 18.0.0 redux: ^5.0.0-beta.0 @@ -9589,16 +9570,16 @@ __metadata: languageName: node linkType: hard -"redux-thunk@npm:3.0.0-alpha.3": - version: 3.0.0-alpha.3 - resolution: "redux-thunk@npm:3.0.0-alpha.3" +"redux-thunk@npm:^3.0.0-beta.0": + version: 3.0.0-beta.0 + resolution: "redux-thunk@npm:3.0.0-beta.0" peerDependencies: - redux: ^4 - checksum: a5be77887b422b3182ff7fae617ec552cd5f830afb326d83af32a430c3eb439c942a38c3691e5c975119e37787974172dbc0139f7782cbfaeea5c1292fa123ed + redux: ^4 || ^5.0.0-beta.0 + checksum: 1609e18a9fb56ab7403d760999996b50e136fcf7411ec9d809e9a4afa4187bf0ab545652c05ffbfca2e0397e59e6baf2ae0d35631a30bf8ba20af1205e98e0fe languageName: node linkType: hard -"redux@npm:5.0.0-beta.0, redux@npm:^5.0.0-beta.0": +"redux@npm:^5.0.0-beta.0": version: 5.0.0-beta.0 resolution: "redux@npm:5.0.0-beta.0" checksum: 11df373e219f2f515ee1bda1a19a1ba5de02d8d5c874800ec353179dcd106eddd54432946fd0ab37c47f99f8fe53f820a6404c14da7f039a46022187e9469d2d @@ -9738,10 +9719,10 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^5.0.0-alpha.2": - version: 5.0.0-alpha.2 - resolution: "reselect@npm:5.0.0-alpha.2" - checksum: c47b66999800e1297721cbc4b2464b520fade9823c598d578759c9fba3eb6be03b184e13c20f30820cc18fe2688fc9fb4475f83e59d8f2347aa0d591e465637d +"reselect@npm:^5.0.0-beta.0": + version: 5.0.0-beta.0 + resolution: "reselect@npm:5.0.0-beta.0" + checksum: 462363aa730af93e396ff0d885f88fb8c43572b07f51c2a890d37f27edc3afecd300085916533e336142b3883f8532f35b5b1a2aaa1a70e9909aea48e5d3b98f languageName: node linkType: hard