Suyash Nachankar
Suyash Nachankar

Reputation: 69

How to use setTimeout() along with React Hooks useEffect and setState?

I want to wait for 10 seconds for my API call to fetch the category list array from backend and store in hook state. If nothing is fetched in 10 sec, I want to set error hook state to true.

But the problem is even after the array is fetched initially, the error state is set to true and categoriesList array in state blanks out after 10 sec.

import React, { useState, useEffect } from "react";

import { doGetAllCategories } from "../helper/adminapicall.js";

const ViewCategories = () => {
  let [values, setValues] = useState({
    categoriesList: "",
    error: false,
  });

  let { categoriesList, error } = values;

  const preloadCategories = () => {
    doGetAllCategories()
      .then((data) => {
        if (data.error) {
          return console.log("from preload call data - ", data.error);
        }
        setValues({ ...values, categoriesList: data.categories });
      })
      .catch((err) => {
        console.log("from preload - ", err);
      });
  };

  useEffect(() => {
    preloadCategories();

    let timerFunc = setTimeout(() => {
      if (!categoriesList && !error) {
        setValues({
          ...values,
          error: "Error fetching category list... try after some time !",
        });
      }
    }, 10000);

    return () => {
      clearTimeout(timerFunc);
    };
  }, []);



//...further code

Upvotes: 4

Views: 17346

Answers (2)

Petro Bianka
Petro Bianka

Reputation: 145

The problem with your code is that you expect to have a change of the state of the component inside the useEffect hook. Instead, you create two variables inside the useEffect that track whether the limit of 10 sec has passed or the data is fetched. In contrary to state variables, you can expect these variables to change because they lie within the same useEffect.

export default function App() {
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState(null);
  React.useEffect(() => {
    let didCancel = false;
    let finished = false;
    async function fetchData() {
      const data = await subscribeAPI();
      if (!didCancel) {
        finished = true;
        setData(data);
      }
    }
    const id = setTimeout(() => {
      didCancel = true;
      if (!finished) {
        setError("Errorrrr");
      }
    }, 10000);

    fetchData();

    return () => {
      clearTimeout(id);
    };
  }, []);

Upvotes: -1

Jonas Wilms
Jonas Wilms

Reputation: 138257

The problem is that the useEffect callback is a closure over categoriesList, so you'll always see the initial categories list inside the callback, and you won't see any changes to it. Now one could add categoriesList as a dependency to the useEffect hook, that way the hook will be recreated on every categoriesList change, and thus you can see the changed version:

useEffect(/*...*/, [categoriesList]);

Now the good thing is, that by updating the hook the timeout also gets canceled, so if the category list is set, we just don't have to create a new timeout:

  useEffect(() => {
    if(!categoriesList && !error) {
      let timerFunc = setTimeout(() => {
        setValues({
          ...values,
          error: "Error fetching category list... try after some time !",
        });
      }, 10000);

      return () => clearTimeout(timerFunc);
  }
}, [!categoriesList, !error]); // some minor optimization, changes to the list don't bother this hook

I recommend you to read this blog post on useEffect by Dan Abramov.

Upvotes: 9

Related Questions