Fetching Resources in a React Suspense World
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 src
attribute. 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.