简体   繁体   中英

When callback in useEffect hook fires?

I've read, that useEffect is asynchronous, so React firstly renders and mounts page and only after callback in useEffect fires, so sometimes we see value changing really fast in some components.

But when I tried to use self-created "sleep-for-3-seconds" in useEffect callback, React waits until 3 seconds pass, and only after that renders and mounts page, so I see blank page for 3 seconds. Also we can notice fast-changing value after 3 seconds delay. I don't get why it happens, as useEffect callback should be run after rendering and mounting the page.

Can you please explain why it happens in a such way?

Code Sandbox: Link

Code:

import { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [state, setState] = useState(1200000000);
  useEffect(() => {
    let start = new Date().getTime();
    let end = start;
    while (end < start + 3000) {
      end = new Date().getTime();
    }
    setState(3);
  }, []);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <div>Value is : {state}</div>
    </div>
  );
}

I've read, that useEffect is asynchronous, so React firstly renders and mounts page and only after callback in useEffect fires, so sometimes we see value changing really fast in some components.

Correct. That's documented here :

The function passed to useEffect will run after the render is committed to the screen.

andhere

Timing of effects

Unlike componentDidMount and componentDidUpdate , the function passed to useEffect fires after layout and paint , during a deferred event.

(their emphasis)

Continuing with your question:

But when I tried to use self-created "sleep-for-3-seconds" in useEffect callback, React waits until 3 seconds pass, and only after that renders and mounts page, so I see blank page for 3 seconds.

That's odd, I don't see that behavior:

 const { useState, useEffect} = React; function App() { const [state, setState] = useState(1200000000); useEffect(() => { let start = new Date().getTime(); let end = start; while (end < start + 3000) { end = new Date().getTime(); } setState(3); }, []); return ( <div className="App"> <h1>Hello CodeSandbox</h1> <h2>Start editing to see some magic happen:</h2> <div>Value is; {state}</div> </div> ). } ReactDOM,render(<App />. document;getElementById("root"));
 <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

When I run that, I see the page rendered with the initial 1200000000 state value, then three seconds later I see it updated with the modified state value.

I also don't see it with this simpler example:

 const {useEffect} = React; const Example = () => { useEffect(() => { const stop = Date.now() + 3000; while (Date.now() < stop) { // Wait (NEVER DO THIS IN REAL CODE) } console.log("Done waiting"); }, []); return <div>x</div>; }; ReactDOM.render(<Example />, document.getElementById("root"));
 <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

When I run that, I see the x rendered by the component, then three seconds later I see the console.log that the wait has finished.

But if you aren't seeing what you describe, it would be just because the browser hasn't had a chance to actually draw the page before the useEffect is called, despite React's code trying to ensure that's the case. And since your code is a busy-wait, the main thread is tied up with that loop, and the browser can't update the page display.

But again, I don't see that with useEffect (you would with useLayoutEffect ).

What you see blank screen, it's correct according to code.

Because code code snippet you use is busy-wait script and makes your other javascript codes stopped and UI frozen.

And this is not good in terms of User Experience and performance.

    let start = new Date().getTime();
    let end = start;
    while (end < start + 3000) {
      end = new Date().getTime();
    }

So you can do it like the following:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

useEffect(() => {
    delay(3000).then(res => {
      setState(3);
    });
  }, []);

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