jka
jka

Reputation: 457

How to conditionally change state in useEffect after a delay in React?

I'm trying to render a loading message after 1.5 seconds of waiting, if the thing is still loading:

useEffect(() => {
  setTimeout(() => {
    if (loading) setSleepMessage("The server seems to be sleeping. Waking it up, wait a second...");
  }, 1500);
}, [loading]);

The variable loading is a boolean in state returned by an Apollo hook, and setSleepMessage is hooked to state too:

import { useQuery } from "@apollo/client";
import React, { useState, useEffect } from "react";
// ...
  const [ sleepMessage, setSleepMessage ] = useState<string>();
  const { data , loading } = useQuery(SOME_QUERY);
  // ...

For some reason my logic fails here; the sleep message is rendered after 1.5 seconds no matter whether loading has changed from true to false at that point. Why is this? Is its value in setTimeout evaluated right away even though the function is to be executed after the timer expires? Or am I missing something else here?

Upvotes: 2

Views: 5077

Answers (3)

Drew Reese
Drew Reese

Reputation: 202836

You need to clean up your effects by returning a function to clear the timeout. I suggest also only initiating the timeout if loading is true.

useEffect(() => {
  let timerId;

  if (loading) {
    timerId = setTimeout(() => {
      setSleepMessage(
        "The server seems to be sleeping. Waking it up, wait a second..."
      );
    }, 1500);
  }
  return () => clearTimeout(timerId);
}, [loading]);

Upvotes: 1

Michael
Michael

Reputation: 1872

Firstly, the query is loading so the value of loading is true, the setTimeout will be put into JS call stack and keep the value of loading is true, after 1,5s it is put into task queue. After some ticks, the call stack is empty, it will put the callback function inside setTimeout back to callstack and setSleepMessage is executed. So that why the sleep message is rendered after 1.5 seconds no matter whether loading is true or false. You should clearTimeout in useEffect to prevent this happens.

You can read more about event loop in JS to dive deeper.

Upvotes: 1

Danilo Gacevic
Danilo Gacevic

Reputation: 494

You didn't clear the previous timeout.

useEffect(() => {
  const timeoutId = setTimeout(() => {
    if (loading) setSleepMessage("The server seems to be sleeping. Waking it up, wait a second...");
  }, 1500);

  return () => clearTimeout(timeoutId);
}, [loading]);

Upvotes: 2

Related Questions