@@ -22,6 +22,7 @@ import React, {
2222 startTransition ,
2323 Suspense ,
2424 useDeferredValue ,
25+ useLayoutEffect ,
2526 type FragmentInstance ,
2627 type JSX ,
2728} from 'react'
@@ -307,81 +308,73 @@ class InnerScrollAndFocusHandlerOld extends React.Component<ScrollAndFocusHandle
307308 }
308309}
309310
310- class InnerScrollAndFocusHandlerNew extends React . Component < ScrollAndFocusHandlerProps > {
311- childrenRef = React . createRef < FragmentInstance > ( )
312-
313- handlePotentialScroll = ( ) => {
314- // Handle scroll and focus, it's only applied once in the first useEffect that triggers that changed.
315- const { focusAndScrollRef, segmentPath } = this . props
316-
317- if ( focusAndScrollRef . apply ) {
318- // segmentPaths is an array of segment paths that should be scrolled to
319- // if the current segment path is not in the array, the scroll is not applied
320- // unless the array is empty, in which case the scroll is always applied
321- if (
322- focusAndScrollRef . segmentPaths . length !== 0 &&
323- ! focusAndScrollRef . segmentPaths . some ( ( scrollRefSegmentPath ) =>
324- segmentPath . every ( ( segment , index ) =>
325- matchSegment ( segment , scrollRefSegmentPath [ index ] )
311+ function InnerScrollAndFocusHandlerNew ( props : ScrollAndFocusHandlerProps ) {
312+ const childrenRef = React . useRef < FragmentInstance > ( null )
313+
314+ useLayoutEffect (
315+ ( ) => {
316+ const { focusAndScrollRef, segmentPath } = props
317+ // Handle scroll and focus, it's only applied once in the first useEffect that triggers that changed.
318+
319+ if ( focusAndScrollRef . apply ) {
320+ // segmentPaths is an array of segment paths that should be scrolled to
321+ // if the current segment path is not in the array, the scroll is not applied
322+ // unless the array is empty, in which case the scroll is always applied
323+ if (
324+ focusAndScrollRef . segmentPaths . length !== 0 &&
325+ ! focusAndScrollRef . segmentPaths . some ( ( scrollRefSegmentPath ) =>
326+ segmentPath . every ( ( segment , index ) =>
327+ matchSegment ( segment , scrollRefSegmentPath [ index ] )
328+ )
326329 )
327- )
328- ) {
329- return
330- }
331-
332- let domNode : FragmentInstance | HTMLElement | null = null
333- const hashFragment = focusAndScrollRef . hashFragment
334-
335- if ( hashFragment ) {
336- domNode = getHashFragmentDomNode ( hashFragment )
337- }
330+ ) {
331+ return
332+ }
338333
339- if ( ! domNode ) {
340- domNode = this . childrenRef . current
341- }
334+ let domNode : FragmentInstance | HTMLElement | null = null
335+ const hashFragment = focusAndScrollRef . hashFragment
342336
343- // If there is no DOM node this layout-router level is skipped. It'll be handled higher-up in the tree.
344- if ( domNode === null ) {
345- return
346- }
337+ if ( hashFragment ) {
338+ domNode = getHashFragmentDomNode ( hashFragment )
339+ }
347340
348- // State is mutated to ensure that the focus and scroll is applied only once.
349- focusAndScrollRef . apply = false
350- focusAndScrollRef . hashFragment = null
351- focusAndScrollRef . segmentPaths = [ ]
341+ if ( ! domNode ) {
342+ domNode = childrenRef . current
343+ }
352344
353- const alignToTop = false
354- disableSmoothScrollDuringRouteTransition (
355- domNode . experimental_scrollIntoView . bind ( domNode , alignToTop ) ,
356- {
357- // We will force layout by querying domNode position
358- dontForceLayout : true ,
359- onlyHashChange : focusAndScrollRef . onlyHashChange ,
345+ // If there is no DOM node this layout-router level is skipped. It'll be handled higher-up in the tree.
346+ if ( domNode === null ) {
347+ return
360348 }
361- )
362349
363- // Mutate after scrolling so that it can be read by `disableSmoothScrollDuringRouteTransition`
364- focusAndScrollRef . onlyHashChange = false
350+ // State is mutated to ensure that the focus and scroll is applied only once.
351+ focusAndScrollRef . apply = false
352+ focusAndScrollRef . hashFragment = null
353+ focusAndScrollRef . segmentPaths = [ ]
365354
366- // Set focus on the element
367- domNode . focus ( )
368- }
369- }
355+ const alignToTop = false
356+ disableSmoothScrollDuringRouteTransition (
357+ domNode . experimental_scrollIntoView . bind ( domNode , alignToTop ) ,
358+ {
359+ // We will force layout by querying domNode position
360+ dontForceLayout : true ,
361+ onlyHashChange : focusAndScrollRef . onlyHashChange ,
362+ }
363+ )
370364
371- componentDidMount ( ) {
372- this . handlePotentialScroll ( )
373- }
365+ // Mutate after scrolling so that it can be read by `disableSmoothScrollDuringRouteTransition`
366+ focusAndScrollRef . onlyHashChange = false
374367
375- componentDidUpdate ( ) {
376- // Because this property is overwritten in handlePotentialScroll it's fine to always run it when true as it'll be set to false for subsequent renders.
377- if ( this . props . focusAndScrollRef . apply ) {
378- this . handlePotentialScroll ( )
379- }
380- }
368+ // Set focus on the element
369+ domNode . focus ( )
370+ }
371+ } ,
372+ // Used to run on every commit. We may be able to be smarter about this
373+ // but be prepared for lots of manual testing.
374+ undefined
375+ )
381376
382- render ( ) {
383- return < Fragment ref = { this . childrenRef } > { this . props . children } </ Fragment >
384- }
377+ return < Fragment ref = { childrenRef } > { props . children } </ Fragment >
385378}
386379
387380const InnerScrollAndFocusHandler = enableNewScrollHandler
0 commit comments