I'm testing a heavy calculation that blocks the render of my UI and using a useTransition
hook to fix it and show a loading while it is updating.
App.tsx
import { useEffect, useState, useTransition } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
const handleClick = () => {
console.log("Starting Transition...");
startTransition(() => {
for (let i = 0; i < 1000000000; i++) {}
setCount((prevCount) => prevCount + 1);
console.log("Ending Transition...");
});
};
useEffect(() => {
console.log(isPending);
}, [isPending]);
return (
<div className="App">
<div>
<h1>{count}</h1>
<button disabled={isPending} onClick={handleClick}>
{isPending ? "Loading..." : "Increment"}
</button>
</div>
{isPending && "Loading"}
</div>
);
}
The problem is the isPending
is turning to true only a short time after the useTransition
is done, and the logs are like this when I click the button:
Logs
Starting Transition...
Ending Transition...
true
false
My question is why isPending
is not turning true when the transition starts and not after it ends?
startTransition
does not cause a synchronous rerender when you call it. Synchronous rerenders are a thing that exists, but they can be a performance problem, and transitions are trying to improve perceived performance, not hurt it.
Instead, when you call startTransition
you tell react "any calls i make to set state inside this function are low priority". React takes note of this, and then calls your function, and then you set state as many times as you like. Once your function returns, react queues up a normal priority rerender to set isPending
to true. After that, it does a low priority render to apply the state changes you made.
So startTransition
is not designed for you to do some large amount of synchronous work. It will not help at all with that. You might call startTransition
when you're done with some heavy work. But that's useful not because it breaks up your synchronous code, but rather because the resulting render will be low priority. And a low priority render is useful if the render itself might take a while, or if you expect the user will be doing high priority things (eg, typing into an input field) which you don't want to slow down.
If you want to split up some synchronous code of yours, you will have to write custom code to do that. For example, you can have a series of setTimeout
s each of which run a bit of code, then queue up the next setTimeout, thus giving the browser a chance to run other code.