reactjsreact-suspense

Maximum wait time for Suspense + useTransition in React


How can I set a time limit for suspended components to be ready before displaying the fallback? Does React have any built-ins to manage this?

What I have in mind is the following sequence of events:

So far, this is possible to do with Suspense and useTransition:

const [page, setPage] = useState("/");
const [isPending, startTransition] = useTransition();

function navigate(url) {
    startTransition(() => {
      setPage(url);
    });
}

<Button onClick={() => navigate("/otherPage")}>
  Go to next page {isPending && 'Loading'}
</Button>

// Clicking this can interrupt the transition
<Button onClick={() => navigate("/")}>
  Go home
</Button>

However, in some cases, /otherPage takes a really long time to load, and I wish for it to fallback to a full-page load via Suspense fallback after 2 seconds of waiting.

How can I do this?


Solution

  • As I was writing the question, I was trying different approaches to solve the problem. I found a solution, but am posting the question anyways because someone else might have a better solution, or someone might find mine useful.

    In the end, I wrote a hook to do the heavy-lifting by leaning onto useOptimistic:

    const useTransitionState = (initialState, timeoutMs) => {
      const [internalState, setInteralState] = useState(initialState);
      const [isPending, startTransition] = useTransition();
      const [pendingState, setPendingState] = useOptimistic(
        internalState,
        (_, newState) => newState
      );
    
      useEffect(() => {
        if (internalState === pendingState || !timeoutMs) return;
        const timeoutId = setTimeout(() => {
          setInteralState(pendingState);
        }, timeoutMs);
        return () => clearTimeout(timeoutId);
      }, [pendingState, internalState]);
    
      const setState = useCallback((state) => {
        startTransition(() => {
          setInteralState(state);
          setPendingState(state);
        });
      });
      return [internalState, setState, isPending ? pendingState : undefined];
    };
    

    Demo on CodeSandbox

    Video demo: