URL: https://github.com/peterbe/use-slow-truth-demo

I use this React hook to reduce the display of spinner animation icons in cases where a little patience means we don't really need to bother indicating that something is loading.

The problem

For example, you might have a TanStack Query that makes XHR requests to the backend, and that backend is generally fast. Many times, it finishes in tens or hundreds of milliseconds. If you prescriptively always show the loading spinner icon, or whatever you might have, it's going to flicker and cause confusion.

For example, your use of useQuery might look like this:


function MyComponent() {
  const { data, isPending, error } = useQuery({
    queryKey: ["something"],
    queryFn: fetcher,
  })

  return <div>
    {isPending && <Spinner/>}
    {error && <Alert error={error}/>}
    {data && <Tabular data={data} />}
  </div>
}

The problem is that if isPending is only true for a very short time, you run the risk of displaying the <Spinner/> so briefly that it just becomes a flickering blur to the user.

The solution

The hook code looks like this:


import { useEffect, useState } from "react";

type Options = {
  delay?: number;
};

/**
 * A hook that throttles the truth. Useful when you want something to be true
 * only if it's been true for a certain delay in milliseconds. Example use:
 *
 *   const stillLoading = useSlowTruth(isLoading);
 *
 * If the value of `isLoading` quickly changes from false, to true, to false;
 * the value of `stillLoading` will remain false the whole time.
 *
 * @param initialState boolean
 * @param options
 * @returns a single boolean that is a delayed mirror of the input, if it's true
 */
export function useSlowTruth(initialState: boolean, { delay = 1000 }: Options) {
  const [isTrue, setIsTrue] = useState(initialState);
  useEffect(() => {
    let mounted = true;
    let timer: number | null = null;
    if (initialState) {
      timer = window.setTimeout(() => {
        if (mounted) {
          setIsTrue(true);
        }
      }, delay);
    } else {
      if (timer !== null) {
        window.clearTimeout(timer);
      }
      setIsTrue(false);
    }
    return () => {
      if (timer !== null) {
        window.clearTimeout(timer);
      }
      mounted = false;
    };
  }, [initialState, delay]);
  return isTrue;
}

I put together a demo app here: https://github.com/peterbe/use-slow-truth-demo

The usage

This change


+import { useSlowTruth } from "./useSlowTruth"
+
function MyComponent() {
  const { data, isPending, error } = useQuery({
    queryKey: ["something"],
    queryFn: fetcher,
  })

+ const isStillPending = useSlowTruth(isPending, { delay: 300 })

  return <div>
-   {isPending && <Spinner/>}
+   {isStillPending && <Spinner/>}
    {error && <Alert error={error}/>}
    {data && <Tabular data={data} />}
  </div>
}

Now, only if the XHR query takes longer than 300ms does it show the <Spinner/> component.

Your email will never ever be published.

Previous:
Claude Opus is 10x faster than OpenAI GPT 5 at non-streaming completions July 24, 2026 Python, AI
Related by category:
How to handle success and failure in @tanstack/react-query useQuery hook September 16, 2024 React
Starting a side project: PissueTracker March 16, 2025 React
An ideal pattern to combine React Router with TanStack Query November 18, 2024 React
You don't need a context or state manager for TanStack Query in scattered React components January 2, 2026 React
Related by keyword:
An ideal pattern to combine React Router with TanStack Query November 18, 2024 React, JavaScript
WebSockets vs. XHR 2019 May 5, 2019 Web development, Web Performance, JavaScript
React.memo instead of React.PureComponent November 2, 2018 React, JavaScript
localForage vs. XHR October 22, 2014 JavaScript