简体   繁体   中英

React setState hook not working with useEffect

I have the following code. The idea is to create a simple loading for a mock component

const [loading, setLoading] = useState(false)

  function mockLoading () {
    setLoading(true)
    console.log('1', loading)
    setTimeout(() => {
      setLoading(false)
      console.log('2', loading)
    }, 3000)
  }

  useEffect(() => {
    mockLoading()
  }, []);

But for some reason, setLoading is not working. Console.log 1 and 2 both are false

A couple of things. One issue is that when you set the state, the state does not immediately update. So your first console.log will see the old state. Not only that, but when your effect is called and mockLoading is called, it will only see the instance of state that existed at the time it was called. So any changes to the variable will never be seen by mockLoading . This is why effects have dependencies. That way when a dependency updates, you'll see it. But I don't think having a dependency here will help given how your code is structured. I'm not 100% clear on your final goal, but to accomplish what you want based on the code you submitted, you'll want to useRef instead of useState . useRef gives you an object who's value is always current. See:

const loadingRef = useRef(false)

function mockLoading () {
    loadingRef.current = true;
    console.log('1', loadingRef.current)
    setTimeout(() => {
        loadingRef.current = false;
      console.log('2', loadingRef.current)
    }, 3000)
}

useEffect(() => {
    mockLoading()
}, []);

Using refs is generally frowned upon unless you absolutely need it. Try to refactor your code to have a dependency on loading in your useEffect call. Though keep in mind you may end up with an infinite loop if your mockLoading always updates loading when it's called. Try to only update loading if it's not already set to the value you want. If your end goal is to just update loading after 3 seconds, try this:

const [loading,setLoading] = useState(false);

useEffect(() => {
    setTimeout(() => {
        setLoading(true);
    },3000);
},[]);

return <span>{loading ? 'Loading is true!' : 'Loading is false!'}</span>;

If you want to inspect the value of loading without rendering it, then you'll need another useEffect with a dependency on loading :

const [loading,setLoading] = useState(false);

useEffect(() => {
    setTimeout(() => {
        setLoading(true);
    },3000);
},[]);

useEffect(() => {
    console.log(loading);
},[loading]);

return <span>{loading ? 'Loading is true!' : 'Loading is false!'}</span>;

Hope this helps.

Declare your handler inside useEffect and log outside

const [loading, setLoading] = useState(false)

  useEffect(() => {
      function mockLoading () {
         setLoading(true)
         console.log('1', loading)
         setTimeout(() => {
         setLoading(false)
         console.log('2', loading)
       }, 3000)
  }
    mockLoading()
  }, []);

  console.log(loading)

Also, you should clean your timeout on unmount

useEffect(() =>{
    const timeout = setTimeout(() =>{}, 0)

    return clearTimeout(timeout)
},[])

Setting state in React is async, so you won't see the changes to the state straight after you made them. To track state changes you need to use the effect hook:

const [loading, setLoading] = useState(false);

function mockLoading() {
  setLoading(true);
  console.log("1", loading);
  setTimeout(() => {
    setLoading(false);
    console.log("2", loading);
  }, 3000);
}

useEffect(() => {
  mockLoading();
}, []);

// Display the current loading state
useEffect(() => {
  console.log("loading", loading);
}, [loading]);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM