Skip to content

Commit

Permalink
Remove additional <div> at each segment level in app (#43717)
Browse files Browse the repository at this point in the history
  • Loading branch information
timneutkens committed Dec 6, 2022
1 parent cbf87e1 commit 5cff316
Showing 1 changed file with 62 additions and 29 deletions.
91 changes: 62 additions & 29 deletions packages/next/client/components/layout-router.tsx
@@ -1,20 +1,18 @@
'use client'

import React, { useContext, useEffect, useRef, use } from 'react'
import type {
ChildProp,
//Segment
} from '../../server/app-render'
import type {
AppRouterInstance,
ChildSegmentMap,
} from '../../shared/lib/app-router-context'
import type {
FlightRouterState,
FlightSegmentPath,
// FlightDataPath,
} from '../../server/app-render'
import type { ErrorComponent } from './error-boundary'
import type { FocusAndScrollRef } from './reducer'

import React, { useContext, useEffect, use } from 'react'
import { findDOMNode as ReactDOMfindDOMNode } from 'react-dom'
import type { ChildProp } from '../../server/app-render'
import {
CacheStates,
LayoutRouterContext,
Expand Down Expand Up @@ -77,6 +75,31 @@ function walkAddRefetch(
return treeToRecreate
}

// TODO-APP: Replace with new React API for finding dom nodes without a `ref` when available
/**
* Wraps ReactDOM.findDOMNode with additional logic to hide React Strict Mode warning
*/
function findDOMNode(
instance: Parameters<typeof ReactDOMfindDOMNode>[0]
): ReturnType<typeof ReactDOMfindDOMNode> {
// Only apply strict mode warning when not in production
if (process.env.NODE_ENV !== 'production') {
const originalConsoleError = console.error
try {
console.error = (...messages) => {
// Ignore strict mode warning for the findDomNode call below
if (!messages[0].includes('Warning: %s is deprecated in StrictMode.')) {
originalConsoleError(...messages)
}
}
return ReactDOMfindDOMNode(instance)
} finally {
console.error = originalConsoleError!
}
}
return ReactDOMfindDOMNode(instance)
}

/**
* Check if the top of the HTMLElement is in the viewport.
*/
Expand All @@ -85,6 +108,36 @@ function topOfElementInViewport(element: HTMLElement) {
return rect.top >= 0
}

class ScrollAndFocusHandler extends React.Component<{
focusAndScrollRef: FocusAndScrollRef
children: React.ReactNode
}> {
componentDidMount() {
// Handle scroll and focus, it's only applied once in the first useEffect that triggers that changed.
const { focusAndScrollRef } = this.props
const domNode = findDOMNode(this)

if (focusAndScrollRef.apply && domNode instanceof HTMLElement) {
// State is mutated to ensure that the focus and scroll is applied only once.
focusAndScrollRef.apply = false
// Set focus on the element
domNode.focus()
// Only scroll into viewport when the layout is not visible currently.
if (!topOfElementInViewport(domNode)) {
const htmlElement = document.documentElement
const existing = htmlElement.style.scrollBehavior
htmlElement.style.scrollBehavior = 'auto'
domNode.scrollIntoView()
htmlElement.style.scrollBehavior = existing
}
}
}

render() {
return this.props.children
}
}

/**
* InnerLayoutRouter handles rendering the provided segment based on the cache.
*/
Expand Down Expand Up @@ -117,26 +170,6 @@ export function InnerLayoutRouter({

const { changeByServerResponse, tree: fullTree, focusAndScrollRef } = context

const focusAndScrollElementRef = useRef<HTMLDivElement>(null)

useEffect(() => {
// Handle scroll and focus, it's only applied once in the first useEffect that triggers that changed.
if (focusAndScrollRef.apply && focusAndScrollElementRef.current) {
// State is mutated to ensure that the focus and scroll is applied only once.
focusAndScrollRef.apply = false
// Set focus on the element
focusAndScrollElementRef.current.focus()
// Only scroll into viewport when the layout is not visible currently.
if (!topOfElementInViewport(focusAndScrollElementRef.current)) {
const htmlElement = document.documentElement
const existing = htmlElement.style.scrollBehavior
htmlElement.style.scrollBehavior = 'auto'
focusAndScrollElementRef.current.scrollIntoView()
htmlElement.style.scrollBehavior = existing
}
}
}, [focusAndScrollRef])

// Read segment path from the parallel router cache node.
let childNode = childNodes.get(path)

Expand Down Expand Up @@ -257,9 +290,9 @@ export function InnerLayoutRouter({

// Ensure root layout is not wrapped in a div as the root layout renders `<html>`
return rootLayoutIncluded ? (
<div ref={focusAndScrollElementRef} data-nextjs-scroll-focus-boundary={''}>
<ScrollAndFocusHandler focusAndScrollRef={focusAndScrollRef}>
{subtree}
</div>
</ScrollAndFocusHandler>
) : (
subtree
)
Expand Down

0 comments on commit 5cff316

Please sign in to comment.