diff --git a/packages/@react-facet/deferred-mount/src/index.spec.tsx b/packages/@react-facet/deferred-mount/src/index.spec.tsx index 7e0dcf83..ada7ef91 100644 --- a/packages/@react-facet/deferred-mount/src/index.spec.tsx +++ b/packages/@react-facet/deferred-mount/src/index.spec.tsx @@ -16,6 +16,27 @@ interface Cb { frameId: number } +jest.useFakeTimers() + +const frames: (() => void)[] = [] +const requestSpy = jest.spyOn(window, 'requestAnimationFrame').mockImplementation((frameRequest) => { + const id = idSeed++ + const cb = () => { + frameRequest(id) + } + frames.push(cb) + return id +}) + +const runRaf = () => { + const cb = frames.pop() + if (cb != null) act(() => cb()) +} + +afterEach(() => { + requestSpy.mockClear() +}) + describe('DeferredMount', () => { it('renders immediately if we dont have a provider', () => { const { container } = render( @@ -25,9 +46,69 @@ describe('DeferredMount', () => { ) expect(container.firstChild).toContainHTML('
Should be rendered
') }) + + it('defers again after initial defer has completed', () => { + const DeferConditionally: React.FC<{ mountDeferred: boolean }> = ({ mountDeferred }) => { + const isDeferringFacet = useIsDeferring() + return ( + <> + (isDeferring ? 'deferring' : 'done'), [], [isDeferringFacet])} + /> + {mountDeferred && ( + +

Conditionally rendered

+
+ )} + + ) + } + + const { container, rerender } = render( + + + , + ) + + expect(container).toContainHTML('deferring') + expect(container).not.toContainHTML('

Conditionally rendered

') + + runRaf() + expect(container).toContainHTML('done') + expect(container).not.toContainHTML('

Conditionally rendered

') + + rerender( + + + , + ) + + expect(container).toContainHTML('deferring') + expect(container).not.toContainHTML('

Conditionally rendered

') + + runRaf() + expect(container).toContainHTML('done') + expect(container).toContainHTML('

Conditionally rendered

') + }) }) describe('DeferredMountWithCallback', () => { + const MOUNT_COMPLETION_DELAY = 1000 + + const MockDeferredComponent = ({ index }: { index: number }) => { + const triggerMountComplete = useNotifyMountComplete() + + useEffect(() => { + const id = setTimeout(triggerMountComplete, MOUNT_COMPLETION_DELAY) + + return () => { + clearTimeout(id) + } + }, [triggerMountComplete, index]) + + return
Callback{index}
+ } + it('renders immediately if we dont have a provider', () => { const { container } = render( @@ -37,61 +118,26 @@ describe('DeferredMountWithCallback', () => { expect(container.firstChild).toContainHTML('
Should be rendered
') }) - it('waits until previous deferred callback finishes', async () => { - jest.useFakeTimers() - - const frames: (() => void)[] = [] - const requestSpy = jest.spyOn(window, 'requestAnimationFrame').mockImplementation((frameRequest) => { - const id = idSeed++ - const cb = () => { - frameRequest(id) - } - frames.push(cb) - return id - }) - - const runRaf = () => { - const cb = frames.pop() - if (cb != null) act(() => cb()) - } - - const MOUNT_COMPLETION_DELAY = 1000 - - const MockDeferredComponent = ({ index }: { index: number }) => { - const triggerMountComplete = useNotifyMountComplete() - - useEffect(() => { - const id = setTimeout(triggerMountComplete, MOUNT_COMPLETION_DELAY) - - return () => { - clearTimeout(id) - } - }, [triggerMountComplete, index]) - - return
Callback{index}
- } - - const SampleComponent = () => { - const isDeferringFacet = useIsDeferring() + const SampleComponent = () => { + const isDeferringFacet = useIsDeferring() - return ( - <> - (isDeferring ? 'deferring' : 'done'), [], [isDeferringFacet])} - /> - - - - - - - - - - - ) - } + return ( + <> + (isDeferring ? 'deferring' : 'done'), [], [isDeferringFacet])} /> + + + + + + + + + + + ) + } + it('waits until previous deferred callback finishes', async () => { const { container } = render( @@ -128,9 +174,6 @@ describe('DeferredMountWithCallback', () => { jest.advanceTimersByTime(MOUNT_COMPLETION_DELAY) runRaf() expect(container).toContainHTML('done') - - jest.useRealTimers() - requestSpy.mockRestore() }) }) diff --git a/packages/@react-facet/deferred-mount/src/index.tsx b/packages/@react-facet/deferred-mount/src/index.tsx index 74bddcbe..a69b9710 100644 --- a/packages/@react-facet/deferred-mount/src/index.tsx +++ b/packages/@react-facet/deferred-mount/src/index.tsx @@ -43,7 +43,6 @@ export function InnerDeferredMountProvider({ frameTimeBudget = DEFAULT_FRAME_TIME_BUDGET, }: DeferredMountProviderProps) { const [isDeferring, setIsDeferring] = useFacetState(true) - const [requestingToRun, setRequestingToRun] = useFacetState(false) const waitingForMountCallback = useRef(false) const deferredMountsRef = useRef([]) @@ -51,7 +50,7 @@ export function InnerDeferredMountProvider({ const pushDeferUpdateFunction = useCallback( (updateFn: UpdateFn) => { // Causes a re-render of this component that will kick-off the effect below - setRequestingToRun(true) + setIsDeferring(true) deferredMountsRef.current.push(updateFn) @@ -69,14 +68,14 @@ export function InnerDeferredMountProvider({ } } }, - [setRequestingToRun], + [setIsDeferring], ) useFacetEffect( - (requestingToRun) => { + (isDeferring) => { // Even if we are not considered to be running, we need to check if there is still // work pending to be done. If there is... we still need to run this effect. - if (!requestingToRun && deferredMountsRef.current.length === 0 && !waitingForMountCallback.current) return + if (!isDeferring && deferredMountsRef.current.length === 0 && !waitingForMountCallback.current) return const work = (startTimestamp: number) => { const deferredMounts = deferredMountsRef.current @@ -131,7 +130,6 @@ export function InnerDeferredMountProvider({ if (deferredMounts.length === 0 && !waitingForMountCallback.current) { setIsDeferring(false) - setRequestingToRun(false) } } @@ -141,8 +139,8 @@ export function InnerDeferredMountProvider({ window.cancelAnimationFrame(frameId) } }, - [frameTimeBudget, setIsDeferring, setRequestingToRun], - [requestingToRun], + [frameTimeBudget, setIsDeferring], + [isDeferring], ) return (