Created
August 18, 2025 15:02
-
-
Save tusharsnx/13b4410c5dbf32510a332766c9d3edf5 to your computer and use it in GitHub Desktop.
An alternative suspense boundary which hides the components from DOM while they are loading
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { useEffect, useState } from "react"; | |
| import { useActivateLoaderBoundary } from "#/components/suspense-activity"; | |
| export function useResource<T>({ | |
| load, | |
| unload, | |
| }: { | |
| load: () => Promise<T> | T; | |
| unload?: () => Promise<void> | void; | |
| }) { | |
| const [asyncState, setAsyncState] = useState<T | null>(null); | |
| // Get the boundary activator. | |
| const activate = useActivateLoaderBoundary(); | |
| useEffect(() => { | |
| let cancelled = false; | |
| async function loadResource() { | |
| const result = await load(); | |
| if (cancelled) { | |
| return; | |
| } | |
| setAsyncState(result); | |
| } | |
| const deactivate = activate(loadResource); | |
| return () => { | |
| cancelled = true; | |
| deactivate(); | |
| }; | |
| }, [load, activate]); | |
| return [asyncState === null, asyncState] as const; | |
| } | |
| async function loadData() { | |
| await new Promise((resolve) => setTimeout(resolve, 2000)); | |
| return "Data loaded 4"; | |
| } | |
| export function VideoComponent() { | |
| // We should get whether we need to show the loading state or not from the SuspenseAltContext. | |
| const [isLoading, asyncState] = useResource({ load: loadData }); | |
| if (isLoading) { | |
| // This is not visible in the DOM. | |
| return <div>VideoComponent Loading...</div>; | |
| } | |
| return ( | |
| <div> | |
| <p>{asyncState}</p> | |
| </div> | |
| ); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| "use client"; | |
| import { createContext, type ReactNode, use, useRef, useState } from "react"; | |
| type Deactivate = () => void; | |
| type LoaderBoundaryActivate = (cb: () => Promise<void> | void) => Deactivate; | |
| const LoaderBoundaryContext = createContext<LoaderBoundaryActivate | null>( | |
| null, | |
| ); | |
| export function useActivateLoaderBoundary() { | |
| const context = use(LoaderBoundaryContext); | |
| if (!context) { | |
| throw new Error( | |
| "useSuspenseTransition must be used within a SuspenseAltProvider", | |
| ); | |
| } | |
| return context; | |
| } | |
| export function LoaderBoundary({ | |
| children, | |
| fallback, | |
| }: { | |
| children: ReactNode; | |
| fallback?: ReactNode; | |
| }) { | |
| const [isPending, setIsPending] = useState(false); | |
| const suspendedActivityCountRef = useRef(0); | |
| function activate(cb: () => Promise<void> | void) { | |
| setIsPending(true); | |
| suspendedActivityCountRef.current += 1; | |
| let unsuspended = false; | |
| function unsuspend() { | |
| if (unsuspended) { | |
| return; | |
| } | |
| unsuspended = true; | |
| suspendedActivityCountRef.current -= 1; | |
| if (suspendedActivityCountRef.current === 0) { | |
| setIsPending(false); | |
| } | |
| } | |
| void Promise.resolve().then(cb).finally(unsuspend); | |
| return unsuspend; | |
| } | |
| return ( | |
| <LoaderBoundaryContext value={activate}> | |
| {/* When pending, children will still be part of the HTML tree, but not the visual tree. */} | |
| <div style={{ display: isPending ? "none" : "contents" }}>{children}</div> | |
| {/* Fallback is only shown when we're pending */} | |
| {isPending && fallback} | |
| </LoaderBoundaryContext> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment