Remove additional <div> at each segment level in app (#43717)

This commit is contained in:
Tim Neutkens 2022-12-06 10:26:37 +01:00 committed by GitHub
parent cbf87e1ebf
commit 5cff316aee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,10 +1,4 @@
'use client'
import React, { useContext, useEffect, useRef, use } from 'react'
import type {
ChildProp,
//Segment
} from '../../server/app-render'
import type {
AppRouterInstance,
ChildSegmentMap,
@ -12,9 +6,13 @@ import type {
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,
@ -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.
*/
@ -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.
*/
@ -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)
@ -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
)