Skip to content

Instantly share code, notes, and snippets.

@ryanflorence
Created October 5, 2018 16:36
Show Gist options
  • Save ryanflorence/9b9bfb83b7c5f6798eb4baa4b1ba60b0 to your computer and use it in GitHub Desktop.
Save ryanflorence/9b9bfb83b7c5f6798eb4baa4b1ba60b0 to your computer and use it in GitHub Desktop.

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.

Basic Suspense Setup

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:

  1. React starts rendering (in memory)
  2. It hits that InvoicesResource.read() call
  3. 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.
  4. 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 a throw, no more code is executed, everything stops.
  5. React waits for the promise to resolve.
  6. The promise resolves.
  7. React tries to render Invoices again.
  8. It hits that InvoicesResource.read() again.
  9. Data is in the cache this time (woo hoo!) so our data can be returned synchronously from ApiResource.read().
  10. 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.

Okay, So What About Redux?

If we were to do this workflow in redux it'd look something like this:

https://gist.github.com/c4c57f5ed2dbcd49b55dab7a2a641b74

Comparison

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.

What about cache invalidation?

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.

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 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.

Buy My Stuff!

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.

https://reach.tech/workshops

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment