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>
);
}
CodePudding user response:
I've read, that
useEffect
is asynchronous, so React firstly renders and mounts page and only after callback inuseEffect
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.
and here
Timing of effects
Unlike
componentDidMount
andcomponentDidUpdate
, the function passed touseEffect
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
).
CodePudding user response:
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);
});
}, []);