I've been playing around with React's new Concurrent Mode and it is amazing. A little mind-bendy, a dash mad, and a whole lot of wonderful.
Your app might get faster even if you don't change anything. 👌
That's because concurrent mode is backwards compatible. Your code works the same ... if you've been following React best practices. If you haven't, god help you.
It's a change in how React schedules updates to your UI.
/../ UI libraries, including React, typically work today. Once they start rendering an update, including creating new DOM nodes and running the code inside components, they can’t interrupt this work. We’ll call this approach “blocking rendering”. In Concurrent Mode, rendering is not blocking. It is interruptible. This improves the user experience. It also unlocks new features that weren’t possible before.
What this means is that you can start new UI updates before the old ones finish.
Say you're building a dataviz with streaming data. You're 5,000 elements in so adding another 1,000 takes a while. You start rendering and a new batch shows up before you're done.
With blocking renders you have to wait.
With concurrent mode you stop the current render and schedule a new one. 👌
It's similar to how D3 transitions always let you schedule a new transition without worry. Each new transition first stops whatever's going on, then starts the new transition from current state.
Try clicking this ball real fast to see what I mean
Not quite concurrent mode but a similar idea.
So what!? This is amazing. This is what we've been waiting for for 2 years my friend.
Not only is concurrent mode a big step forward for React itself, it also gives us some new abilities. The community is still figuring out the details, but what I've seen buzzing on twitter looks exciting.
My favorite new ability is React Suspense for data fetching. Dan just published an example where he loads code for the page at the same time as its data. And then concurrent mode figures out the rest.
Various react routes will have to implement this to work properly. You can't quite do it unless you know what data you'll need before your components render.
That's the biggest shift in mindset: Fetch data before/while rendering. Not after initial render like we used to:
Dunno about you, but I'm tired of writing that code. Fingers crossed we get some amazing new libraries around Suspense. 🤞
While we wait, here's some concurrent mode stuff you can do right now.
The easiest way to try concurrent mode is to fire up a CodeSandbox and choose React's experimental channel.
Same thing works in your
package.json. Please don't use it in production yet. Experiment and play only. There's bugs to fix and features to come :)
We played around on a recent livestream and created a couple tiny examples using React Suspense's new fetch-as-you-render ability.
Our context is a D3 scatterplot because a) it looks pretty and b) reactfordataviz.com 😛
Don't worry, if you're not into dataviz. The same core concepts apply.
We created a fake data streaming API, rendered each unit of data with the new
<Suspense /> component, and let React handle the rest. Works great.
Try it out here 👇
We split our fake API into two endpoints:
- fetch a total count
- fetch datapoints one by one until done
A real world API analog would be an API that deals with large data. First you make a request for some meta data (counts, dates, ranges, etc), then page through the large dataset.
For simplicity and stress testing, we fetch each item individually. Timeouts simulate
Those function calls simulate API requests. We have to wrap them in a way that
I borrowed the promise wrapper that Dan Abramov uses in official docs on Suspense. You'd build something more robust for real life use. Or, if you're like me, wait for libraries to show up. 😇
The idea behind
wrapPromise is to create a uniform interface for Suspense to hook into your promise. This way it can tell whether it's still waiting, there was an error, or you're ready to render.
I imagine this makes it easier to integrate with a variety of data fetching mechanisms. Wrapper makes them conform to Suspense's API.
Once we've got the API wrappers set up, we can render our React app in parallel with fetching the data. In some cases even in parallel with fetching the code itself. (code splitting)
We start the request at top level, not inside an effect. This way it starts as soon as the file loads.
Presumably that's just before you're ready to render. The approach won't work if you're loading 500 pages all in a single bundle and only 1 of them is visible.
Top-level fetching assumes your router handles code splitting and loads only the files it needs.
To render our data without UI glitching, we use the
<Suspense> component ✌️
We wrap everything in Suspense to show a loading state while we fetch meta data. The
count in our case.
<DataList> component itself tries to read from our API and render what it gets.
This is \important and very easy to get wrong. Suspense goes around the component that's reading data.
Yeah, happened the very first time I tried to build with React Suspense without following the docs. You get that experiment in your inbox next week, it's super neat.
Now here's where it gets real fun: You can nest suspended components.
Notice how we're wrapping each
<DataItem> in its own suspense?
That's because each item is in charge of its own API fetching. Seems kinda bonkers, but this new approach (also popular in GraphQL circles) is really powerful.
Each React component should take care of its own data fetching. The times when you had a central authority take care of all data are behind us. Edge computing is now. Even if just in our UI tree. 🦾
Each item hooks into its own
index'd API promise that we created in
items.read() returns an array of wrapped promises.
[index] gets the one for this item.
.read() waits for that promise to return some data.
<Suspense> component handles suspending and fallback renders while this component waits for data to show up. Once data shows up, Suspense renders
<DataItem> which uses a little D3 magic to translate
[0, 1] numbers to a bigger scale and renders an SVG circle.
Now of course reading data one by one like that is slow.
Instead, you can use a paging approach and load a many data points at once. 100 worked pretty well with our fake API.
Rendering code looks the same, just gets an extra loop.
And we changed the fake API to return multiple datapoints per batch.
I encourage you to give React Suspense and concurrent mode in general a try. It's pretty great, almost ready for the real world, and it might make you rethink how you structure your apps.
Encouraging you to push data loading into the edges of your component tree seems like a particularly powerful pattern. Load data as close to where you're using it as possible.
Stop copy pasting D3 examples, create data visualizations of your own
Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization.
About the Author
Hi, I’m Swizec Teller. I help coders become software engineers.
Story time 👇
React+D3 started as a bet in April 2015. A friend wanted to learn React and challenged me to publish a book. A month later React+D3 launched with 79 pages of hard earned knowledge.
In April 2016 it became React+D3 ES6. 117 pages and growing beyond a single big project it was a huge success. I kept going, started live streaming, and publishing videos on YouTube.
In 2017, after 10 months of work, React + D3v4 became the best book I'd ever written. At 249 pages, many examples, and code to play with it was designed like a step-by-step course. But I felt something was missing.
So in late 2018 I rebuilt the entire thing as React for Data Visualization — a proper video course. Designed for busy people with real lives like you. Over 8 hours of video material, split into chunks no longer than 5 minutes, a bunch of new chapters, and techniques I discovered along the way.
React for Data Visualization is the best way to learn how to build scalable dataviz components your whole team can understand.
Some of my work has been featured in 👇