Home > Enterprise >  Unable to set loading indicator with useState hook in React
Unable to set loading indicator with useState hook in React

Time:04-04

I'm learning React and cannot understand why my code isn't working.

  const [isLoading, setIsLoading] = useState(false);

  const expensiveFn = () => {
    let z = 0;
    for (let i = 0; i < 10000; i  ) {
      z = i;
      console.log("z", z);
    }
  };

  const submitHandler = (e) => {
    e.preventDefault();

    console.log("isLoading 1", isLoading);

    setIsLoading(true);
    
    console.log("isLoading 2", isLoading);

    expensiveFn();

    setIsLoading(false);

    console.log("isLoading 3", isLoading);
  };

  return (
    <div>
      <form action="">
        <button type="submit" onClick={submitHandler}>
          Submit
        </button>
      </form>

      {isLoading && <div>Loading...</div>}
    </div>
  );

I am not able to get the loading message because the isLoading is not changing.

What am I doing wrong and please, why?

Update: I've combined your answers and tried to change my code accordingly, but now I am getting the state changed, but the message rendered is not displaying.

  const [isLoading, setIsLoading] = useState(false);

  const expensiveFn = () => {
    for (let i = 0; i < 1000; i  ) {
      console.log("i");
    }

    return new Promise((resolve, reject) => {
      resolve();
    });
  };

  const submitHandler = (e) => {
    e.preventDefault();

    setIsLoading(true);
  };

  useEffect(() => {
    if (isLoading) {
      console.log("isLoading", isLoading);
      expensiveFn().then(() => setIsLoading(false));
    }
  }, [isLoading]);

  return (
    <div>
      <form action="">
        <button type="submit" onClick={submitHandler}>
          Submit
        </button>
      </form>

      {isLoading && <div>Loading...</div>}
    </div>
  );

Update - 2 I figured it is the loop that block my render now

Update - 3 Thank you for your answers, I wouldn't be able to solve it without you. I now know(with your help) how to deal with this.

CodePudding user response:

Because setState is an asynchronous function, for checking the state, you can run useEffect

This is a great article on this

CodePudding user response:

keyword : setState, setState asynchronous.
reference : https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

Fix up code)

import { useEffect, useState } from "react";

function App() {
  const [isLoading, setIsLoading] = useState(false);

  const expensiveFn = () => {
    let z = 0;
    for (let i = 0; i < 10000; i  ) {
      z = i;
      console.log("z", z);
    }
  };

  const submitHandler = e => {
    e.preventDefault();

    setIsLoading(true);
  };

  useEffect(() => {
    if (isLoading) {
      expensiveFn();
      setIsLoading(false);
    }
  }, [isLoading]);

  return (
    <div>
      <form action="">
        <button type="submit" onClick={submitHandler}>
          Submit
        </button>
      </form>

      {isLoading && <div>Loading...</div>}
    </div>
  );
}

export default App;

CodePudding user response:

To answer your question in the simplest way, useState is asynchronous. Which means, when you call setIsLoading(true), isLoading is not immediately set to true. You should change your expensiveFn to return a promise instead and when that promise is fulfilled, that's when you set isLoading to false.

setIsLoading(true);
expensiveFn().then(() => setIsLoading(false));

CodePudding user response:

This can seem a bit tricky if you are a beginner, from your code

setIsLoading(true);
expensiveFn();
setIsLoading(false);

you see, setStates in react are asynchronous and js engine being single threaded the order in which above lines of code executes would be

expensiveFn()
setIsLoading(true)
setIsLoading(false)

for this reason you would always see isLoading: false in console.
solution: You can utilze useEffect hook like this

useEffect(() => {
   if(isLoading) {
      expensiveFn()
      setIsLoading(false)
   }
}, [isLoading])

Now, expensiveFn() and setIsLoading(false) are only executed after isLoading is set to true

  • Related