Demoing startTransition is hard. Modern computers are too fast π
The demo I sent you last week doesn't work. It's an aberration. A fluke of dev mode.
Further testing showed that every render takes less than 1ms. startTransition
and time slicing didn't even have time to kick in.
π©
So I built a better demo on stream, but a subtle gotcha got in the way. Dan from the React team helped me figure it out and we even found a bug in the alpha. Great success all around!
You can try the final React 18 startTransition demo here
The demo shows you what happens when every state change updates 1,000,000+ nodes. Slider on the left grows the tree, makes the problem worse β exponentially. Slider on top leans the tree, updates every node. Use it to see what happens :)
Toggle the Use startTransition
checkbox to compare behavior with and without the new feature. You should see your inputs laaaaaag without startTransition
. When it's enabled, the urgent input update happens fast and the slow fractal updates later.
If you don't see slowness, enable the artificial 0.1ms delay. That'll do it.
The original fractal tree stress test is code from 2016 and I'm happy to report upgrading to React 18 worked without changes.
You can see the full code on GitHub
Why it's slow
More seriously: DOM updates arenβt that big a deal here.
β Swizec Teller (@Swizec) June 15, 2021
Itβs a stress test for any rendering engine β deeply nested recursive data structure. It would be slow even if you never draw
The pythagoras tree is a fractal. A deeply nested data structure that brings any rendering framework to its knees. Drawing to the DOM is the least of your worries.
The left slider sets nesting level. At 20 (max) your computer will slow down for sure. My 2020 top-spec MacBook sure did π
Each layer spawns 2 branches. Every update touches every branch. That's 2^20-1 = 1,048,575
updates for every change in lean angle.
That's what makes this a great stress test :)
- user code: calculates all that data
- framework code: propagates the updates
- browser code: redraws the DOM nodes
Yes WebGL or Canvas would make the draw step faster, but this is not a graphics demo. We're showing something important that React 18 unlocks.
Think of each square as a separate React component in your app. A user action might update many of them. Each component on its own is fast to render, but the work adds up. And there's no one place to optimize π±
startTransition
enables React to break down this rendering work into small chunks.
This keeps the slider UI responsive even while your components are executing. Note that this is true even when you toggle the artificial delay (0.1 ms per square)! Even the fastest library that does updates synchronously will freeze when the slowdown happens in your code.
React can pull it off because it breaks up the work instead of doing it all at once. π€
Modern computers are fast, though, so you can get away with a lot before you'll need to think about startTransition
. Here's a comparison from last year:
How we debugged the demo
You can look at Chrome's performance tab to see what's up. You're looking for large chunks of synchronous work in event handlers β that's an opportunity for startTransition
.
When you enable startTransition
, those big chunks of work split into quick urgent updates followed by the computationally heavy work.
Quick way to test you're leveraging startTransition
βοΈ
When I first showed my demo to Dan we found a fun alpha bug. An old Webpack polyfill from react-scripts 0.7 triggered a bug in React 18 alpha. Updates getting batched weirdly. They'll fix this before going stable.
Always remember: realistic timing happens in production.
A startTransition gotcha
startTransition
lets you mark calculations and updates as not urgent. React performs them later.
You can use this for expensive data transformation, large computations, or complex rendering. Anything goes.
startTransition(() => {
// do slow work
// this code executes synchronously
// but state updates are marked as less urgent
})
Sounds easy when you read about it, then you get it wrong on stream π
Use separate state for non-urgent updates
My first attempt looked kinda like this:
const [treeLean, setTreeLean] = useState(0)
function changeTreeLean(event) {
const value = Number(event.target.value);
// update visuals
if (enableStartTransition) {
React.startTransition(() => {
setTreeLean(value);
});
} else {
setTreeLean(value);
}
}
// ...
<input type="range" value={treeLean} onChange={changeTreeLean} />
<Pythagoras lean={treeLean} ... />
Change input, call changeTreeLean
as the event handler. Tell React to update state inside a transition.
And everything's laggy π€
Because both components depend on the same state ...
You have to split that state:
const [treeLean, setTreeLean] = useState(0)
const [treeLeanInput, setTreeLeanInput] = useState(0)
function changeTreeLean(event) {
const value = Number(event.target.value);
setTreeLeanInput(value)
// update visuals
if (enableStartTransition) {
React.startTransition(() => {
setTreeLean(value);
});
} else {
setTreeLean(value);
}
}
// ...
<input type="range" value={treeLeanInput} onChange={changeTreeLean} />
<Pythagoras lean={treeLean} ... />
Looks weird to split state like that, but it makes sense. You're saying it's okay for these 2 components to be visually out of sync.
Now the input field and the tree update separately. startTransition
makes a big difference.
Use React.memo
const Pythagoras = React.memo(() => {
// same component code as usual
})
Wrap expensive components in React.memo
to give startTransition
a chance to kick in. React needs to know it's safe to split the render step.
Show transitioning state
I thought this was neat. You can show the user that a redraw is happening.
const [isLeaning, startLeaning] = useTransition()
// ...
function changeTreeLean(event) {
const value = Number(event.target.value);
setTreeLeanInput(value); // update input
// update visuals
if (enableStartTransition) {
startLeaning(() => {
setTreeLean(value);
});
} else {
setTreeLean(value);
}
}
// ...
<svg style={ opacity: isLeaning ? 0.7 : 1 }
The useTransition
hook lets you access transition state. "Are we done yet?"
Instead of calling startTransition
, you call the function returned from the hook βΒ startLeaning
. Then you can access done-ness with the boolean βΒ isLeaning
.
You'll notice the tree fades out on big re-renders.
My biggest surprise?
Take React code from 2016, update to React 18 alpha, touch nothing else ... and it works. No bugs π±
Had to update react-scripts
from 0.7 to 4.0 though. An old Webpack polyfill was killing startTransition
.
That's why we torture alpha versions my friend. For bugs like that βοΈ
Cheers,
~Swizec
PS: React For Dataviz is my course on pushing React to the max and fun stuff like thisβ. I think it's getting a new module or two when React 18 comes out :)
Continue reading about A better React 18 startTransition demo
Semantically similar articles hand-picked by GPT-4
- Why I'm excited about React 18 β talk
- Screw web performance, just wait a little π
- Trying out React 18 Alpha
- Animating 2048 SVG nodes in React, Preact, Inferno, Vue, Angular 2, and CycleJS β a side-by-side comparison
- A Dancing Rainbow Snake β An Example of Minimal React and D3v4 transitions
Learned something new? Want to become a React expert?
Learning from tutorials is easy. You follow some steps, learn a smol lesson, and feel like you got dis πͺ
Then comes the interview, a real world problem, or a question from the boss. Your mind goes blank. Shit, how does this work again ...
Happens to everyone. Building is harder than recognizing and the real world is a mess! Nothing fits in neat little boxes you learned about in tutorials.
That's where my emails come in β lessons from experience. Building production software :)
Leave your email and get the React email Series - a series of curated essays on building with React. Borne from experience, seasoned with over 12+ years of hands-on practice building the engineering side of growing SaaS companies.
Get Curated React Essays
Get a series of curated essays on React. Lessons and insights from building software for production. No bullshit.
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help π swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers π ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are β€οΈ