Fix fullscreen infinite render loop in video embeds (#10807)

This commit is contained in:
Samuel Newman
2026-06-09 16:51:36 +03:00
parent ccb4423c72
commit f3e659ca18
3 changed files with 26 additions and 25 deletions
-8
View File
@@ -650,14 +650,6 @@
"count": 2
}
},
"src/components/hooks/useFullscreen.ts": {
"@typescript-eslint/no-floating-promises": {
"count": 2
},
"react-hooks/set-state-in-effect": {
"count": 1
}
},
"src/components/hooks/useLandingEntry.native.ts": {
"react-hooks/set-state-in-effect": {
"count": 1
@@ -108,10 +108,15 @@ export function useActiveVideoWeb() {
return {
active: activeViewId === id,
setActive: () => {
setActive: useCallback(() => {
setActiveView(id)
},
}, [setActiveView, id]),
currentActiveView: activeViewId,
sendPosition: (y: number) => sendViewPosition(id, y),
sendPosition: useCallback(
(y: number) => {
sendViewPosition(id, y)
},
[sendViewPosition, id],
),
}
}
+18 -14
View File
@@ -1,10 +1,4 @@
import {
useCallback,
useEffect,
useRef,
useState,
useSyncExternalStore,
} from 'react'
import {useCallback, useEffect, useRef, useSyncExternalStore} from 'react'
import {IS_WEB, IS_WEB_FIREFOX, IS_WEB_SAFARI} from '#/env'
@@ -13,28 +7,38 @@ function fullscreenSubscribe(onChange: () => void) {
return () => document.removeEventListener('fullscreenchange', onChange)
}
function getFullscreenSnapshot() {
return Boolean(document.fullscreenElement)
}
export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) {
if (!IS_WEB) throw new Error("'useFullscreen' is a web-only hook")
const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () =>
Boolean(document.fullscreenElement),
const isFullscreen = useSyncExternalStore(
fullscreenSubscribe,
getFullscreenSnapshot,
)
const scrollYRef = useRef<null | number>(null)
const [prevIsFullscreen, setPrevIsFullscreen] = useState(isFullscreen)
// Tracked via a ref rather than state so that reacting to a fullscreen change
// never schedules its own render. Scheduling a render in response to the
// external store value (the old `setPrevIsFullscreen` pattern) was a seed for
// the commit-phase update loop reported in APP-315 / APP-5PP / APP-7ZB.
const prevIsFullscreenRef = useRef(isFullscreen)
const toggleFullscreen = useCallback(() => {
if (isFullscreen) {
document.exitFullscreen()
void document.exitFullscreen()
} else {
if (!ref) throw new Error('No ref provided')
if (!ref.current) return
scrollYRef.current = window.scrollY
ref.current.requestFullscreen()
void ref.current.requestFullscreen()
}
}, [isFullscreen, ref])
useEffect(() => {
const prevIsFullscreen = prevIsFullscreenRef.current
if (prevIsFullscreen === isFullscreen) return
setPrevIsFullscreen(isFullscreen)
prevIsFullscreenRef.current = isFullscreen
// Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen
// Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium
@@ -46,7 +50,7 @@ export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) {
}
}, 100)
}
}, [isFullscreen, prevIsFullscreen])
}, [isFullscreen])
return [isFullscreen, toggleFullscreen] as const
}