At my latest workshops I've been getting the question "so does this stuff 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, go give Dan Abramov's JSConf Iceland talk a few minutes of your time.
There are just three parts to using suspense: a cache, a resource, and a component.
Here's a cache:
https://gist.github.com/a8b1bb9356e7f1b7194bc69df7848655
Here's a resource:
https://gist.github.com/30549ac268ee1b2e3feeb49cee933420
It takes a function that returns a promise and that's it.
Finally, here's a component that uses the resource and the cache:
https://gist.github.com/104ed890edb865e11d9ed96060e9e6ef
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 url is the key), so it will call the function we provided to
createResource
and fire off the asynchronous fetch. - AND THEN THE CACHE WILL THROW THE PROMISE WE RETURNED (Yeah, I didn't realize you could throw anything other than errors either, but you can
throw window
if you want.) After athrow
, no more code is executed, everything stops. - React waits for the promise to resolve.
- The promise resolves.
- React tries to render
Invoices
again. - It hits that
InvoicesResource.read()
again. - Data is in the cache this time (woo hoo!) 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 is so cool and feels amazing when you start using it.
There's more to say about handling placeholders 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.
If we were to do this workflow in redux it'd look something like this:
https://gist.github.com/c4c57f5ed2dbcd49b55dab7a2a641b74
While they work quite differently, notice we have all the same pieces:
Redux | Suspense |
---|---|
store | cache |
dispatch | Resource.read() |
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. In my experiments, there is so much less boilerplate to data fetching with Suspense, whether I'm coming from Redux or just component state.
So, if your primary use-case for Redux is using it as a cache of API 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.
The first argument to createCache
is invalidate
. In my experiments with Suspense, I set up my cache to rerender 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.
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 unneccesary.
Some folks are doing more complex things with Redux (like synchronizing state to both their API and localStorage), so Suspense probably won't replace it all.
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 experiences Suspense provides.
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.