Artificial Delay

Add a minimum delay to a mutation's pending UI.

When building pending UI for certain interface elements, like the newsletter signup form above, a mutation that's nearly instant can trigger a confusing "flash of loading state". If the pending UI is too fast, it doesn't inspire confidence that the operation completed successfully, especially if a spinner's animation rate suggests that the operation should have taken longer than it did.

To avoid this flash of loading state, you can wrap your mutation with minDelay. This function uses Promise.all to run the operation and delay in parallel, so if the underlying mutation takes longer than the minimum delay, the user is not punished and forced to wait any longer than they need to.

Usage

function Form() {
  let [isSaving, setIsSaving] = useState(false);

  async function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setIsSaving(true);

    let data = Object.entries(new FormData(e.currentTarget));

    await minDelay(signup(data.email), 1000);

    setIsSaving(false);
  }

  return (
    <form onSubmit={handleSubmit}>
      <p>Sign up for our newsletter</p>
      <input type="email" name="email" />
      <button>Sign up</button>
    </form>
  );
}

Code:

async function minDelay<T>(promise: Promise<T>, ms: number) {
  let delay = new Promise((resolve) => setTimeout(resolve, ms));
  let [p] = await Promise.all([promise, delay]);

  return p;
}