Skip to content

Commit

Permalink
Add focus and scroll management for new router (#38642)
Browse files Browse the repository at this point in the history
  • Loading branch information
timneutkens committed Jul 14, 2022
1 parent e06983b commit fae919c
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 33 deletions.
33 changes: 18 additions & 15 deletions packages/next/client/components/app-router.client.tsx
Expand Up @@ -3,10 +3,12 @@ import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-we
import {
AppRouterContext,
AppTreeContext,
CacheNode,
FullAppTreeContext,
} from '../../shared/lib/app-router-context'
import type { AppRouterInstance } from '../../shared/lib/app-router-context'
import type {
CacheNode,
AppRouterInstance,
} from '../../shared/lib/app-router-context'
import type { FlightRouterState, FlightData } from '../../server/app-render'
import { reducer } from './reducer'
import {
Expand Down Expand Up @@ -117,19 +119,19 @@ export default function AppRouter({
children: React.ReactNode
hotReloader?: React.ReactNode
}) {
const [{ tree, cache, pushRef, canonicalUrl }, dispatch] = React.useReducer<
typeof reducer
>(reducer, {
tree: initialTree,
cache: {
data: null,
subTreeData: children,
parallelRoutes:
typeof window === 'undefined' ? new Map() : initialParallelRoutes,
},
pushRef: { pendingPush: false, mpaNavigation: false },
canonicalUrl: initialCanonicalUrl,
})
const [{ tree, cache, pushRef, focusRef, canonicalUrl }, dispatch] =
React.useReducer<typeof reducer>(reducer, {
tree: initialTree,
cache: {
data: null,
subTreeData: children,
parallelRoutes:
typeof window === 'undefined' ? new Map() : initialParallelRoutes,
},
pushRef: { pendingPush: false, mpaNavigation: false },
focusRef: { focus: false },
canonicalUrl: initialCanonicalUrl,
})

useEffect(() => {
initialParallelRoutes = null!
Expand Down Expand Up @@ -302,6 +304,7 @@ export default function AppRouter({
value={{
changeByServerResponse,
tree,
focusRef,
}}
>
<AppRouterContext.Provider value={appRouter}>
Expand Down
40 changes: 27 additions & 13 deletions packages/next/client/components/layout-router.client.tsx
@@ -1,4 +1,4 @@
import React, { useContext } from 'react'
import React, { useContext, useEffect, useRef } from 'react'
import type { ChildProp } from '../../server/app-render'
import type { ChildSegmentMap } from '../../shared/lib/app-router-context'
import type {
Expand Down Expand Up @@ -61,8 +61,20 @@ export function InnerLayoutRouter({
isActive: boolean
path: string
}) {
const { changeByServerResponse, tree: fullTree } =
useContext(FullAppTreeContext)
const {
changeByServerResponse,
tree: fullTree,
focusRef,
} = useContext(FullAppTreeContext)
const focusAndScrollRef = useRef<HTMLDivElement>(null)

useEffect(() => {
if (focusRef.focus && focusAndScrollRef.current) {
focusRef.focus = false
focusAndScrollRef.current.focus()
focusAndScrollRef.current.scrollIntoView()
}
}, [focusRef])

let childNode = childNodes.get(path)

Expand Down Expand Up @@ -197,16 +209,18 @@ export function InnerLayoutRouter({
}

return (
<AppTreeContext.Provider
value={{
tree: tree[1][parallelRouterKey],
childNodes: childNode.parallelRoutes,
// TODO-APP: overriding of url for parallel routes
url: url,
}}
>
{childNode.subTreeData}
</AppTreeContext.Provider>
<div ref={focusAndScrollRef}>
<AppTreeContext.Provider
value={{
tree: tree[1][parallelRouterKey],
childNodes: childNode.parallelRoutes,
// TODO-APP: overriding of url for parallel routes
url: url,
}}
>
{childNode.subTreeData}
</AppTreeContext.Provider>
</div>
)
}

Expand Down
25 changes: 24 additions & 1 deletion packages/next/client/components/reducer.ts
Expand Up @@ -298,10 +298,20 @@ const walkTreeWithFlightDataPath = (
return tree
}

type PushRef = {
pendingPush: boolean
mpaNavigation: boolean
}

export type FocusRef = {
focus: boolean
}

type AppRouterState = {
tree: FlightRouterState
cache: CacheNode
pushRef: { pendingPush: boolean; mpaNavigation: boolean }
pushRef: PushRef
focusRef: FocusRef
canonicalUrl: string
}

Expand Down Expand Up @@ -349,6 +359,7 @@ export function reducer(
return {
canonicalUrl: href,
pushRef: state.pushRef,
focusRef: state.focusRef,
cache: state.cache,
tree: tree,
}
Expand Down Expand Up @@ -377,6 +388,7 @@ export function reducer(
return {
canonicalUrl: href,
pushRef: { pendingPush, mpaNavigation: false },
focusRef: { focus: true },
cache: state.cache,
tree: optimisticTree,
}
Expand All @@ -393,6 +405,7 @@ export function reducer(
return {
canonicalUrl: href,
pushRef: { pendingPush, mpaNavigation: false },
focusRef: { focus: true },
cache: cache,
tree: mutable.patchedTree,
}
Expand Down Expand Up @@ -430,6 +443,7 @@ export function reducer(
return {
canonicalUrl: href,
pushRef: { pendingPush, mpaNavigation: false },
focusRef: { focus: true },
cache: cache,
tree: optimisticTree,
}
Expand All @@ -446,6 +460,7 @@ export function reducer(
return {
canonicalUrl: flightData,
pushRef: { pendingPush: true, mpaNavigation: true },
focusRef: { focus: false },
cache: state.cache,
tree: state.tree,
}
Expand Down Expand Up @@ -474,6 +489,7 @@ export function reducer(
return {
canonicalUrl: href,
pushRef: { pendingPush, mpaNavigation: false },
focusRef: { focus: true },
cache: cache,
tree: newTree,
}
Expand All @@ -490,6 +506,7 @@ export function reducer(
return {
canonicalUrl: state.canonicalUrl,
pushRef: state.pushRef,
focusRef: state.focusRef,
tree: state.tree,
cache: state.cache,
}
Expand All @@ -500,6 +517,7 @@ export function reducer(
return {
canonicalUrl: flightData,
pushRef: { pendingPush: true, mpaNavigation: true },
focusRef: { focus: false },
cache: state.cache,
tree: state.tree,
}
Expand All @@ -525,6 +543,7 @@ export function reducer(
return {
canonicalUrl: state.canonicalUrl,
pushRef: state.pushRef,
focusRef: state.focusRef,
tree: newTree,
cache: cache,
}
Expand All @@ -546,6 +565,7 @@ export function reducer(
return {
canonicalUrl: href,
pushRef: { pendingPush, mpaNavigation: false },
focusRef: { focus: true },
cache: cache,
tree: mutable.patchedTree,
}
Expand All @@ -566,6 +586,7 @@ export function reducer(
return {
canonicalUrl: flightData,
pushRef: { pendingPush: true, mpaNavigation: true },
focusRef: { focus: false },
cache: state.cache,
tree: state.tree,
}
Expand Down Expand Up @@ -597,6 +618,8 @@ export function reducer(
return {
canonicalUrl: href,
pushRef: { pendingPush, mpaNavigation: false },
// TODO-APP: Revisit if this needs to be true in certain cases
focusRef: { focus: false },
cache: cache,
tree: newTree,
}
Expand Down
2 changes: 2 additions & 0 deletions packages/next/shared/lib/app-router-context.ts
@@ -1,4 +1,5 @@
import React from 'react'
import type { FocusRef } from '../../client/components/reducer'
import type { FlightRouterState, FlightData } from '../../server/app-render'

export type ChildSegmentMap = Map<string, CacheNode>
Expand Down Expand Up @@ -36,6 +37,7 @@ export const FullAppTreeContext = React.createContext<{
previousTree: FlightRouterState,
flightData: FlightData
) => void
focusRef: FocusRef
}>(null as any)

if (process.env.NODE_ENV !== 'production') {
Expand Down
5 changes: 1 addition & 4 deletions test/e2e/app-dir/rsc-basic.test.ts
Expand Up @@ -193,10 +193,7 @@ describe('app dir - react server components', () => {

it('should support next/link in server components', async () => {
const linkHTML = await renderViaHTTP(next.url, '/next-api/link')
const linkText = getNodeBySelector(
linkHTML,
'body > div > a[href="/root"]'
).text()
const linkText = getNodeBySelector(linkHTML, 'body a[href="/root"]').text()

expect(linkText).toContain('home')

Expand Down

0 comments on commit fae919c

Please sign in to comment.