The Suspense is Killing Redux
At my latest workshops I’ve been getting this question:
So does Suspense kill Redux?
First, that’s a rude way to put it. But I get it, the suspense is killing you — or maybe Redux.
React Suspense is all about handling transitions between views that have asynchronous data requirements — which redux doesn’t even attempt to handle. But, to make that work, Suspense is incidentally concerned with handling client-side data — which Redux is very interested in.
If you haven’t heard about React Suspense, give Dan Abramov’s JSConf Iceland talk a few minutes of your time.
Basic Suspense Setup
There are three parts to using suspense: a cache, a resource, and a component.
Here’s a cache:
Here’s a resource:
It takes a function that returns a promise and that’s it. Finally, here’s a component that uses the resource and the cache:
Behold, Suspense.
If we were to render this app, here’s a play-by-play:
- React starts rendering (in memory).
- It hits that
InvoicesResource.read()
call. - The cache will be empty for the key (the id is the key) so it will call the function we provided to
createResource
, firing off the asynchronous fetch. - AND THEN THE CACHE WILL THROW THE PROMISE WE RETURNED (Yeah, I’ve never thought about throwing anything but errors either, but you can
throw window
if you want.) After athrow
, no more code is executed. - React waits for the promise to resolve.
- The promise resolves.
- React tries to render
Invoices
again (in memory). - It hits
InvoicesResource.read()
again. - Data is in the cache this time so our data can be returned synchronously from
ApiResource.read()
. - React renders the page to the DOM
Pretty cool, yeah? The ability to treat our asynchronous data as synchronous function calls feels amazing when you start using it.
There’s more to say about handling placeholders and spinners for when data is loading, but we aren’t going to get into that here. It’s enough to say that React will leave the old screen on the page until the new data lands, or it will transition to placeholders if the data is taking longer than a user-specified amount of time.
Okay, So What About Redux?
If we were to do this workflow in redux it’d look something like this:
Comparison
While they work quite differently, notice we have all the same pieces:
- store → cache
- mapStateToProps → Resource.read()
- action → function passed to resource
The reducer and dispatch just sort of disappear because we don’t reduce over actions anymore, we read from resources. When resources aren’t available yet, rendering suspends and waits for data to land. In a way, the cache + resources handle dispatch, reducers, and actions all at once.
Also note that in Suspense we no longer need the lifecycle hooks. When the Invoice
is rendered with a different invoiceId
the cache will be empty for that key, kicking off a new request automatically. This is significantly simpler than diffing props in lifecycle hooks and dispatching when they change.
In my experiments, there is a lot less to worry about when fetching data with Suspense, whether I'm coming from Redux or component state.
So, if your primary use-case for Redux is using it as a client-side cache of server-side data then Suspense could replace your redux usage. I’d consider it because you’d have simpler code and the ability to tame all those spinners.
What about cache invalidation?
The first argument to createCache
is and invalidation function. In my experiments, I set up my cache to re-render the whole app from the top with a new, empty cache whenever data mutations occur. It sounds a little extreme, but it makes it impossible to ever have my client cache and server data be out of sync — which is awesome.
Additionally, the way suspense handles the update feels great: the old page is still around and interactive while the new version is being rendered in memory. When the new data lands, the page updates with fresh data from the server. Don’t forget, that’s how plain ol’ server rendered apps work, too.
Finally, this approach makes middleware that synchronizes client and server data unnecessary.
It Doesn’t Replace Everything
Some folks are doing more complex things with Redux (like synchronizing state to both their API and localStorage), so Suspense won’t replace every use-case of Redux.
However, in my experience, the majority of my clients and people I talk to are using Redux for little more than a client-side cache of server-side data. If that’s you, I think you’ll enjoy the simplicity and user experience Suspense provides.
Attend a Workshop
If you’d like to test drive Suspense and learn how to get your app ready for it, while also covering all the component patterns of today, consider attending a workshop. I’m hitting most of the United States between now and the end of November. The revenue generated supports my development of Reach UI.