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

Update packaging, build config, and entry points for v9 #2038

Merged
merged 22 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
RSC-specific workarounds (#2050)
* map context instances using `createContext` as key
* use `{}` as ReactReduxContext in environments where no context exists (removes `Proxy` use)
* switch React imports to namespace imports to prevent hook import detection
* use `globalThis` only when availbalbe
  • Loading branch information
phryneas authored Jul 29, 2023
commit 2ac527bc1681b9cf82b269369814419fa8e7a2bc
39 changes: 19 additions & 20 deletions src/components/Context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, version as ReactVersion } from 'react'
import * as React from 'react'
import type { Context } from 'react'
import type { Action, AnyAction, Store } from 'redux'
import type { Subscription } from '../utils/Subscription'
Expand All @@ -15,34 +15,33 @@ export interface ReactReduxContextValue<
noopCheck: CheckFrequency
}

const ContextKey = Symbol.for(`react-redux-context-${ReactVersion}`)
const gT = globalThis as { [ContextKey]?: Context<ReactReduxContextValue> }
const ContextKey = Symbol.for(`react-redux-context`)
const gT: {
[ContextKey]?: Map<
typeof React.createContext,
Context<ReactReduxContextValue>
>
} = (typeof globalThis !== "undefined" ? globalThis : /* fall back to a per-module scope (pre-8.1 behaviour) if `globalThis` is not available */ {}) as any;

function getContext() {
let realContext = gT[ContextKey]
function getContext(): Context<ReactReduxContextValue> {
if (!React.createContext) return {} as any

const contextMap = (gT[ContextKey] ??= new Map<
typeof React.createContext,
Context<ReactReduxContextValue>
>())
let realContext = contextMap.get(React.createContext)
if (!realContext) {
realContext = createContext<ReactReduxContextValue>(null as any)
realContext = React.createContext<ReactReduxContextValue>(null as any)
if (process.env.NODE_ENV !== 'production') {
realContext.displayName = 'ReactRedux'
}
gT[ContextKey] = realContext
contextMap.set(React.createContext, realContext)
}
return realContext
}

export const ReactReduxContext = /*#__PURE__*/ new Proxy(
{} as Context<ReactReduxContextValue>,
/*#__PURE__*/ new Proxy<ProxyHandler<Context<ReactReduxContextValue>>>(
{},
{
get(_, handler) {
const target = getContext()
// @ts-ignore
return (_target, ...args) => Reflect[handler](target, ...args)
},
}
)
)
export const ReactReduxContext = /*#__PURE__*/ getContext()

export type ReactReduxContextInstance = typeof ReactReduxContext

Expand Down
6 changes: 3 additions & 3 deletions src/components/Provider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Context, ReactNode } from 'react'
import React, { useMemo } from 'react'
import * as React from 'react'
import type { ReactReduxContextValue } from './Context'
import { ReactReduxContext } from './Context'
import { createSubscription } from '../utils/Subscription'
Expand Down Expand Up @@ -42,7 +42,7 @@ function Provider<A extends Action = AnyAction, S = unknown>({
stabilityCheck = 'once',
noopCheck = 'once',
}: ProviderProps<A, S>) {
const contextValue = useMemo(() => {
const contextValue = React.useMemo(() => {
const subscription = createSubscription(store)
return {
store,
Expand All @@ -53,7 +53,7 @@ function Provider<A extends Action = AnyAction, S = unknown>({
}
}, [store, serverState, stabilityCheck, noopCheck])

const previousState = useMemo(() => store.getState(), [store])
const previousState = React.useMemo(() => store.getState(), [store])

useIsomorphicLayoutEffect(() => {
const { subscription } = contextValue
Expand Down
36 changes: 18 additions & 18 deletions src/components/connect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable valid-jsdoc, @typescript-eslint/no-unused-vars */
import hoistStatics from 'hoist-non-react-statics'
import type { ComponentType } from 'react'
import React, { useContext, useMemo, useRef } from 'react'
import * as React from 'react'
import { isValidElementType, isContextConsumer } from 'react-is'

import type { Store } from 'redux'
Expand Down Expand Up @@ -533,15 +533,15 @@ function connect<
props: InternalConnectProps & TOwnProps
) {
const [propsContext, reactReduxForwardedRef, wrapperProps] =
useMemo(() => {
React.useMemo(() => {
// Distinguish between actual "data" props that were passed to the wrapper component,
// and values needed to control behavior (forwarded refs, alternate context instances).
// To maintain the wrapperProps object reference, memoize this destructuring.
const { reactReduxForwardedRef, ...wrapperProps } = props
return [props.context, reactReduxForwardedRef, wrapperProps]
}, [props])

const ContextToUse: ReactReduxContextInstance = useMemo(() => {
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 &&
Expand All @@ -553,7 +553,7 @@ function connect<
}, [propsContext, Context])

// Retrieve the store and ancestor subscription via context, if available
const contextValue = useContext(ContextToUse)
const contextValue = React.useContext(ContextToUse)

// The store _must_ exist as either a prop or in context.
// We'll check to see if it _looks_ like a Redux store first.
Expand Down Expand Up @@ -587,13 +587,13 @@ function connect<
? contextValue.getServerState
: store.getState

const childPropsSelector = useMemo(() => {
const childPropsSelector = React.useMemo(() => {
// The child props selector needs the store reference as an input.
// Re-create this selector whenever the store changes.
return defaultSelectorFactory(store.dispatch, selectorFactoryOptions)
}, [store])

const [subscription, notifyNestedSubs] = useMemo(() => {
const [subscription, notifyNestedSubs] = React.useMemo(() => {
if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY

// This Subscription's source should match where store came from: props vs. context. A component
Expand All @@ -615,7 +615,7 @@ function connect<

// Determine what {store, subscription} value should be put into nested context, if necessary,
// and memoize that value to avoid unnecessary context updates.
const overriddenContextValue = useMemo(() => {
const overriddenContextValue = React.useMemo(() => {
if (didStoreComeFromProps) {
// This component is directly subscribed to a store from props.
// We don't want descendants reading from this store - pass down whatever
Expand All @@ -632,14 +632,14 @@ function connect<
}, [didStoreComeFromProps, contextValue, subscription])

// Set up refs to coordinate values between the subscription effect and the render logic
const lastChildProps = useRef<unknown>()
const lastWrapperProps = useRef(wrapperProps)
const childPropsFromStoreUpdate = useRef<unknown>()
const renderIsScheduled = useRef(false)
const isProcessingDispatch = useRef(false)
const isMounted = useRef(false)
const lastChildProps = React.useRef<unknown>()
const lastWrapperProps = React.useRef(wrapperProps)
const childPropsFromStoreUpdate = React.useRef<unknown>()
const renderIsScheduled = React.useRef(false)
const isProcessingDispatch = React.useRef(false)
const isMounted = React.useRef(false)

const latestSubscriptionCallbackError = useRef<Error>()
const latestSubscriptionCallbackError = React.useRef<Error>()

useIsomorphicLayoutEffect(() => {
isMounted.current = true
Expand All @@ -648,7 +648,7 @@ function connect<
}
}, [])

const actualChildPropsSelector = useMemo(() => {
const actualChildPropsSelector = React.useMemo(() => {
const selector = () => {
// Tricky logic here:
// - This render may have been triggered by a Redux store update that produced new child props
Expand Down Expand Up @@ -676,7 +676,7 @@ function connect<
// about useLayoutEffect in SSR, so we try to detect environment and fall back to
// just useEffect instead to avoid the warning, since neither will run anyway.

const subscribeForReact = useMemo(() => {
const subscribeForReact = React.useMemo(() => {
const subscribe = (reactListener: () => void) => {
if (!subscription) {
return () => {}
Expand Down Expand Up @@ -741,7 +741,7 @@ function connect<

// Now that all that's done, we can finally try to actually render the child component.
// We memoize the elements for the rendered child component as an optimization.
const renderedWrappedComponent = useMemo(() => {
const renderedWrappedComponent = React.useMemo(() => {
return (
// @ts-ignore
<WrappedComponent
Expand All @@ -753,7 +753,7 @@ function connect<

// If React sees the exact same element reference as last time, it bails out of re-rendering
// that child, same as if it was wrapped in React.memo() or returned false from shouldComponentUpdate.
const renderedChild = useMemo(() => {
const renderedChild = React.useMemo(() => {
if (shouldHandleStateChanges) {
// If this component is subscribed to store updates, we need to pass its own
// subscription instance down to our descendants. That means rendering the same
Expand Down
4 changes: 2 additions & 2 deletions src/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// The useSyncExternalStoreWithSelector has to be imported, but we can use the
// non-shim version. This shaves off the byte size of the shim.

import { useSyncExternalStore } from 'react'
import * as React from 'react'
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector'

import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates'
Expand All @@ -13,7 +13,7 @@ import { initializeUseSelector } from './hooks/useSelector'
import { initializeConnect } from './components/connect'

initializeUseSelector(useSyncExternalStoreWithSelector)
initializeConnect(useSyncExternalStore)
initializeConnect(React.useSyncExternalStore)

// Enable batched updates in our subscriptions for use
// with standard React renderers (ReactDOM, React Native)
Expand Down
4 changes: 2 additions & 2 deletions src/utils/useIsomorphicLayoutEffect.native.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useLayoutEffect } from 'react'
import * as React from 'react'

// Under React Native, we know that we always want to use useLayoutEffect

export const useIsomorphicLayoutEffect = useLayoutEffect
export const useIsomorphicLayoutEffect = React.useLayoutEffect
6 changes: 4 additions & 2 deletions src/utils/useIsomorphicLayoutEffect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useLayoutEffect } from 'react'
import * as React from 'react'

// React currently throws a warning when using useLayoutEffect on the server.
// To get around it, we can conditionally useEffect on the server (no-op) and
Expand All @@ -16,4 +16,6 @@ export const canUseDOM = !!(
typeof window.document.createElement !== 'undefined'
)

export const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect
export const useIsomorphicLayoutEffect = canUseDOM
? React.useLayoutEffect
: React.useEffect