From e3f62087aa8936af19e38f8c64a72743762044df Mon Sep 17 00:00:00 2001 From: Zack Tanner <1939140+ztanner@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:10:36 -0700 Subject: [PATCH] remove redux devtools from router reducer --- .../next/src/client/components/app-router.tsx | 16 +- .../components/use-reducer-with-devtools.ts | 153 ------------------ .../next/src/client/components/use-reducer.ts | 35 ++++ .../src/shared/lib/router/action-queue.ts | 6 - 4 files changed, 39 insertions(+), 171 deletions(-) delete mode 100644 packages/next/src/client/components/use-reducer-with-devtools.ts create mode 100644 packages/next/src/client/components/use-reducer.ts diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index 42357ab3090ef..2d9b4f530d349 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -39,11 +39,7 @@ import { PathnameContext, PathParamsContext, } from '../../shared/lib/hooks-client-context.shared-runtime' -import { - useReducerWithReduxDevtools, - useUnwrapState, - type ReduxDevtoolsSyncFn, -} from './use-reducer-with-devtools' +import { useReducer, useUnwrapState } from './use-reducer' import { ErrorBoundary, type ErrorComponent } from './error-boundary' import { isBot } from '../../shared/lib/router/utils/is-bot' import { addBasePath } from '../add-base-path' @@ -75,10 +71,8 @@ function isExternalURL(url: URL) { function HistoryUpdater({ appRouterState, - sync, }: { appRouterState: AppRouterState - sync: ReduxDevtoolsSyncFn }) { useInsertionEffect(() => { if (process.env.__NEXT_APP_NAV_FAIL_HANDLING) { @@ -108,9 +102,7 @@ function HistoryUpdater({ } else { window.history.replaceState(historyState, '', canonicalUrl) } - - sync(appRouterState) - }, [appRouterState, sync]) + }, [appRouterState]) return null } @@ -219,7 +211,7 @@ function Router({ actionQueue: AppRouterActionQueue assetPrefix: string }) { - const [state, dispatch, sync] = useReducerWithReduxDevtools(actionQueue) + const [state, dispatch] = useReducer(actionQueue) const { canonicalUrl } = useUnwrapState(state) // Add memoized pathname/query for useSearchParams and usePathname. const { searchParams, pathname } = useMemo(() => { @@ -606,7 +598,7 @@ function Router({ return ( <> - + diff --git a/packages/next/src/client/components/use-reducer-with-devtools.ts b/packages/next/src/client/components/use-reducer-with-devtools.ts deleted file mode 100644 index 9e5636d711cac..0000000000000 --- a/packages/next/src/client/components/use-reducer-with-devtools.ts +++ /dev/null @@ -1,153 +0,0 @@ -import type { Dispatch } from 'react' -import React, { use } from 'react' -import { useRef, useEffect, useCallback } from 'react' -import type { - AppRouterState, - ReducerActions, - ReducerState, -} from './router-reducer/router-reducer-types' -import type { AppRouterActionQueue } from '../../shared/lib/router/action-queue' -import { isThenable } from '../../shared/lib/is-thenable' - -export type ReduxDevtoolsSyncFn = (state: AppRouterState) => void - -function normalizeRouterState(val: any): any { - if (val instanceof Map) { - const obj: { [key: string]: any } = {} - for (const [key, value] of val.entries()) { - if (typeof value === 'function') { - obj[key] = 'fn()' - continue - } - if (typeof value === 'object' && value !== null) { - if (value.$$typeof) { - obj[key] = value.$$typeof.toString() - continue - } - if (value._bundlerConfig) { - obj[key] = 'FlightData' - continue - } - } - obj[key] = normalizeRouterState(value) - } - return obj - } - - if (typeof val === 'object' && val !== null) { - const obj: { [key: string]: any } = {} - for (const key in val) { - const value = val[key] - if (typeof value === 'function') { - obj[key] = 'fn()' - continue - } - if (typeof value === 'object' && value !== null) { - if (value.$$typeof) { - obj[key] = value.$$typeof.toString() - continue - } - if (value.hasOwnProperty('_bundlerConfig')) { - obj[key] = 'FlightData' - continue - } - } - - obj[key] = normalizeRouterState(value) - } - return obj - } - - if (Array.isArray(val)) { - return val.map(normalizeRouterState) - } - - return val -} - -declare global { - interface Window { - __REDUX_DEVTOOLS_EXTENSION__: any - } -} - -export interface ReduxDevToolsInstance { - send(action: any, state: any): void - init(initialState: any): void -} - -export function useUnwrapState(state: ReducerState): AppRouterState { - // reducer actions can be async, so sometimes we need to suspend until the state is resolved - if (isThenable(state)) { - const result = use(state) - return result - } - - return state -} - -export function useReducerWithReduxDevtools( - actionQueue: AppRouterActionQueue -): [ReducerState, Dispatch, ReduxDevtoolsSyncFn] { - const [state, setState] = React.useState(actionQueue.state) - const devtoolsConnectionRef = useRef(undefined) - const enabledRef = useRef(undefined) - - useEffect(() => { - if (devtoolsConnectionRef.current || enabledRef.current === false) { - return - } - - if ( - enabledRef.current === undefined && - typeof window.__REDUX_DEVTOOLS_EXTENSION__ === 'undefined' - ) { - enabledRef.current = false - return - } - - devtoolsConnectionRef.current = window.__REDUX_DEVTOOLS_EXTENSION__.connect( - { - instanceId: 8000, // Random number that is high to avoid conflicts - name: 'next-router', - } - ) - if (devtoolsConnectionRef.current) { - devtoolsConnectionRef.current.init( - normalizeRouterState(actionQueue.state) - ) - - if (actionQueue) { - actionQueue.devToolsInstance = devtoolsConnectionRef.current - } - } - - return () => { - devtoolsConnectionRef.current = undefined - } - }, [actionQueue]) - - const dispatch = useCallback( - (action: ReducerActions) => { - actionQueue.dispatch(action, setState) - }, - [actionQueue] - ) - - // Sync is called after a state update in the HistoryUpdater, - // for debugging purposes. Since the reducer state may be a Promise, - // we let the app router use() it and sync on the resolved value if - // something changed. - // Using the `state` here would be referentially unstable and cause - // undesirable re-renders and history updates. - const sync = useCallback((resolvedState) => { - if (devtoolsConnectionRef.current) { - devtoolsConnectionRef.current.send( - { type: 'RENDER_SYNC' }, - normalizeRouterState(resolvedState) - ) - } - }, []) - - return [state, dispatch, sync] -} diff --git a/packages/next/src/client/components/use-reducer.ts b/packages/next/src/client/components/use-reducer.ts new file mode 100644 index 0000000000000..38906e8695eab --- /dev/null +++ b/packages/next/src/client/components/use-reducer.ts @@ -0,0 +1,35 @@ +import type { Dispatch } from 'react' +import React, { use } from 'react' +import { useCallback } from 'react' +import type { + AppRouterState, + ReducerActions, + ReducerState, +} from './router-reducer/router-reducer-types' +import type { AppRouterActionQueue } from '../../shared/lib/router/action-queue' +import { isThenable } from '../../shared/lib/is-thenable' + +export function useUnwrapState(state: ReducerState): AppRouterState { + // reducer actions can be async, so sometimes we need to suspend until the state is resolved + if (isThenable(state)) { + const result = use(state) + return result + } + + return state +} + +export function useReducer( + actionQueue: AppRouterActionQueue +): [ReducerState, Dispatch] { + const [state, setState] = React.useState(actionQueue.state) + + const dispatch = useCallback( + (action: ReducerActions) => { + actionQueue.dispatch(action, setState) + }, + [actionQueue] + ) + + return [state, dispatch] +} diff --git a/packages/next/src/shared/lib/router/action-queue.ts b/packages/next/src/shared/lib/router/action-queue.ts index acc5b3afd8bc2..40a5eb9c22250 100644 --- a/packages/next/src/shared/lib/router/action-queue.ts +++ b/packages/next/src/shared/lib/router/action-queue.ts @@ -7,7 +7,6 @@ import { ACTION_NAVIGATE, ACTION_RESTORE, } from '../../../client/components/router-reducer/router-reducer-types' -import type { ReduxDevToolsInstance } from '../../../client/components/use-reducer-with-devtools' import { reducer } from '../../../client/components/router-reducer/router-reducer' import { startTransition } from 'react' import { isThenable } from '../is-thenable' @@ -16,7 +15,6 @@ export type DispatchStatePromise = React.Dispatch export type AppRouterActionQueue = { state: AppRouterState - devToolsInstance?: ReduxDevToolsInstance dispatch: (payload: ReducerActions, setState: DispatchStatePromise) => void action: (state: AppRouterState, action: ReducerActions) => ReducerState pending: ActionQueueNode | null @@ -85,10 +83,6 @@ async function runAction({ actionQueue.state = nextState - if (actionQueue.devToolsInstance) { - actionQueue.devToolsInstance.send(payload, nextState) - } - runRemainingActions(actionQueue, setState) action.resolve(nextState) }