Fetching Resources in a React Suspense World

Andrei Duca
4 min readApr 26, 2020

--

Photo by Who’s Denilo ? on Unsplash

In my previous article, I talked about how we can implement a data fetching pattern that we can use today with React Suspense.

Suspense is not just about fetching data in a declarative way, but about fetching resources in general, including data. We saw how we can wrap api functions with the useAsyncResource hook, but what about other resources like images, sound files, and external scripts?

Well, let’s try to build that.

Building with what we have

Our custom useAsyncResource hook from my previous article is already doing enough, and we saw how flexible it is, being able to work with any function that returns a Promise.

useAsyncResource is versatile enough to work with api functions that don't accept arguments:

This looks simple enough when working with data fetching, because, well, fetch will make an api call, then return the response from the server, response that we read and display in our JSX. But what about images for example? How can we "read" the image "response" from the server?

Well, we don’t. And we don’t actually need to!

Preloading images

See, when displaying images in HTML, we have an img tag with an srcattribute. Nothing more declarative than that! As soon as the browser sees that src tag, it will start downloading the image, displaying it progressively as its data comes through the wire. The image then becomes a resource of the page and, in most cases, the browser caches it. When the image is needed later, the browser will serve it immediately, not needing to download all its data again.

So what we really want in our case is actually to not render any img tag until we have the image already downloaded in the browser's cache. We want to display the image all at once, showing a loader until we have it in full. All we need to do is tell the browser to download the image (our resource) in the background, then tell us when that's done, so we can safely render our img tag.

Luckily, we don’t need to actually render an img tag into the DOM to start downloading an image. We can do it in memory:

Turning it into a Promise

This is fine how it is, but we need a Promise. Actually, a function that returns a Promise. Let’s create one:

Nothing more simple than that. We now have a… function, that returns a… Promise, that just resolves with the input (the file path) when it finishes. A function, that returns a Promise, that resolves with a string. Just like we were doing with our api functions all along.

Using it with our hook

By now you probably guessed that this will immediately work with our custom useAsyncResource hook:

And that’s it. The user image won’t be rendered at all until the browser downloads it in the background.

A better user experience

But there will still be a flash of content here: the user name and bio will show for a brief moment along an image placeholder, then the actual image will show on the page. Wouldn’t it be nice if we didn’t show the entire user component until both the user data AND the image are downloaded, so we avoid a flash of content?

Well, remember that our custom hook caches the resources. So calling useAsyncResource(imageResource, someFilePath) multiple times won't trigger multiple identical api calls.

In our case, we’re safe to remove the nested Suspense boundary and just render the profile photo alongside the other user info. The outer Suspense boundary that wraps the entire user object will try to render the user component until it doesn’t throw anymore. This will call useAsyncResource multiple times for our user.profilePic file resource. But we don't care, because the first call will cache it, then all subsequent calls will use the first cached version.

So what we end up with is the simpler (and more user friendly) version:

You can’t get more declarative than that!

Images vs. scripts

Unlike an image, a script resource cannot be simply loaded in the background just by setting the src attribute. Instead, we will have to add the script tag to our DOM. But we can still hook into the onload handler to know when the script was loaded on our page.

Using this scriptResource helper becomes just as easy:

Notice we don’t do anything with the const jQ, but we still need to call props.jQueryResource() so it can throw, rendering our fallback until the library is loaded on the page.

Of course, this is a contrived example. But it illustrates how you can dynamically load a 3rd party library before accessing anything from it.

Conclusion

As we showed in the previous article, adopting React Suspense can make your code simpler to write, read, and understand. It helps you avoid common traps that the async world can set for you, and lets you focus only on writing your stateful UI. And bringing images (or any other resources for that matter) into the mix shouldn’t be any harder than that.

This post was originally published on dev.to.

--

--