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)
}