We are struggling with a strange issue in using setTimeout
function in Javascript at the React.
As using codes inside the setTimeout
, it runs much slower than running the codes without the setTimeout
!
As a comparison, the performance is resulted in:
Using
setTimeout
: 1391 ms
without usingsetTimeout
: 15 ms
In API callback (eg. axios
), the setTimeout version happens as well!
A simple sample code is shown in the following:
codesandbox
Can anyone explain what is happening?
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [value, setValue] = useState("");
const [time, setTime] = useState("");
const startSample = () => {
const startTimeStamp = new Date().valueOf();
for (let i = 0; i < 5000; i++) {
setValue(`test-${i}`);
// console.log(`test-${i}`);
}
const endTimeStamp = new Date().valueOf();
setTime(`${endTimeStamp - startTimeStamp}ms`);
};
const handleClick1 = () => {
startSample();
};
const handleClick2 = () => {
setTimeout(() => {
startSample();
});
};
return (
<div style={{ textAlign: "left" }}>
<p>{value || "Please push that button!"}</p>
<div>
<button id="startBtn" onClick={handleClick1}>
Start Normal
</button>
<button id="startBtn1" onClick={handleClick2}>
Start With setTimeout
</button>
</div>
<p>Result: {time}</p>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Calling setTimeout with a delay of 0 (zero) milliseconds doesn't execute the callback function after the given interval. The execution depends on the number of waiting tasks in the queue.
It's not supposed to be particularly accurate. There are a number of factors limiting how soon the browser can execute the code; quoting from MDN: In addition to "clamping", the timeout can also fire later when the page (or the OS/browser itself) is busy with other tasks.
To explain: If you call setTimeout() with a time of 0 ms, the function you specify is not invoked right away. Instead, it is placed on a queue to be invoked “as soon as possible” after any currently pending event handlers finish running.
React batches renders that are queued up within an event callback, so all of your calls to setValue
in the click handler result in a single render once your handler finishes.
However, I do not believe React batches renders from within a setTimeout call. So it is rendering after each call to setValue
in your setTimeout handler.
See this issue: https://github.com/facebook/react/issues/14259
You should be able to make the setTimeout version faster by writing it like so:
const handleClick2 = () => {
setTimeout(() => ReactDOM.unstable_batchedUpdates(() => startSample()));
}
If you have a bunch of async ajax responses coming back and want to apply them after they all arrive, then you might have code like this:
const [a, setA] = useState();
const [b, setB] = useState();
const [c, setC] = useState();
const [d, setD] = useState();
useEffect(() => {
(async () => {
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();
const d = await fetchD();
// wrap all of the state updates in batchUpdates
// so that we only get one render instead of 4
ReactDOM.unstable_batchUpdates(() => {
setA(a);
setB(b);
setC(c);
setD(d);
});
})()
}), []);
React currently will batch state updates if they're triggered from within a React-based event, like a button click or input change. It will not batch updates if they're triggered outside of a React event handler, like a setTimeout().
Consider the following
const startSample = () => {
const startTimeStamp = new Date().valueOf();
const arr = []
for (let i = 0; i < 5000; i++) {
arr.push(i)
// console.log(`test-${i}`);
}
setValue(arr)
const endTimeStamp = new Date().valueOf();
setTime(`${endTimeStamp - startTimeStamp}ms`);
};
Now both times are about the same right? As pointed out by Brandon React doesn't seem to wait all udpates finishes before triggering a new render, resulting in five thousand renders instead of just one. So use an accumulator to perform the iterations and set the state just once
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With