The usage of a generic loader is the worst feedback an application can provide to a user. It’s the laziest strategy that an engineering team can use to say that the application is loading something.
In this article, I intend to describe how to create prettier loading feedback using React Suspense, Skeletons, and SWR. I will not approach each tool in depth. I want to explain how they can work well together. So, before you read, it’s interesting to understand something about React Hooks and React Suspense. If you are not familiar with those topics, I recommend reading the official documentation; this is enough to understand the magic behind each feature.
What do we want to build?
Our main goal is to create better loading feedback to avoid the flashlight effect while the app is loading some data. Moreover, we wish to create a pretty transient render whilst loading data, even after an error occurs. Consequently, we will design five states to perform a loading effect:
- An initial loading;
- The presentation of the data fetched;
- An intermediate loading, once we already have a data cached;
- A fallback for the initial loading;
- An intermediate fallback while revalidating data.
So, we need to understand how each one is connected by exposing these five artifacts we need in order to create a better loading experience.
React Suspense + SWR
SWR (stale-while-revalidate) will help us with two things. First, integrate hooks that perform async functions with React Suspense; second, to render a cached data while we revalidate it. This mechanics is so simple, SWR returns data from cache (stale) and then dispatch an action (revalidate) to finally update the new data. This feature is the core of SWR, but we can go as far as possible by caching and revalidating data.
First, I will introduce three files: the
App.tsx with a universal config for SWR; the
ProfileContainer.tsx wraps the data that we want to load with React Suspense and handles the initial loading; and the
ProfileData.tsx which is responsible for fetching the data and present it.
So, as we can see in the image aside, we created a component that loads the data and performs a loading with the laziest form. The only differential here is the usage of React Suspense, where we avoid managing the loading state.
We didn’t try anything different so far. But, at this moment, I’ve shown to you how to integrate React Suspense and SWR. In the next steps, we will show how this will help us with intermediate loads.
Initial loading with Skeletons
The skeletons will give us a picture of what is coming next. In our example, we will create a component that prepares the container to receive the date with lots of loaders, one for each kind of content. To link this Skeleton to the component that presents the data, we need to update the fallback props of Suspense component.
Now, we can see what will be loaded soon. First, we filled the body just with silver bones for, after seconds, fulfill the body with useful data. This behavior is enough to hold the anxiety of the user not to refresh the page.
It’s beautiful, it’s useful, and it’s easy to implement. In this example, we have used a simple and customizable library of React Skeletons. There are a lot of Skeletons libraries and, if you prefer, you can build yours.
The most important is to understand the logic of Skeletons. It’s not about creating a new kind of loader; it’s about preparing the body to receive the data.
Once we already have some data presented, if we want to revalidate this data, we don’t need to use the initial loader again. Why? The initial loader (with skeletons) prepares the container to receive the data, once we already received any data, why don’t we reuse it while we revalidate? This way, we don’t reproduce the flashlight effect as another kind of loader.
This time the hook
useSWR will not throw an exception that Suspense handles. This hook will reuse the data (now the name makes sense, right?) and expose a variable that enables us to perform this revalidation.
Now, we can see the data being reused instead of rendering the component
SkeletonLoading. But, we still provide feedback while the data is revalidating, two times. One by displaying the spinner next to the most important text and other by disabling the button that performs the revalidating action.
In this example, the data is the same, but sometimes, the new data can be extremely different from the stale data. I think this strategy is universal, and we can use it even if there’s a batch of data to revalidate. In my scenarios, tables, and lists that perform this behavior promote a better experience than a generic spinner or reusing the initial loader with skeletons.
And what if something went wrong? If the API answer something strange, or the connection goes down or the firewall blocks any request? In the initial loading, we will get a blank screen. This absence of feedback is the worst experience that we can provide to the user. They probably can solve this issue by refreshing the page (now or in five minutes). But we can do something better than that.
In the code below, we will introduce an error boundary. After creating this component, we will wrap the Suspense component.
While we don’t have any data loaded yet, we will keep the loading state in the background while displaying a friendly message to the user and the possibility to retry to load data without refreshing the page.
As we can see, this flow is not abrupt, and the user can understand what is happening on the page.
So, we already have cached data, and we can use it if something goes wrong when we are revalidating any data. In most cases, this treatment is genuinely optional. In some edge cases, we can provide more specific feedback like a toaster or a flash notification.
SWR provides a variable with the error that happened. We can treat this error or use it to promote a subtle warning in the presentation component.
In our example, we just used a warning icon where the intermediate loader should appear. The user still can retry to revalidate the date.
You can display the warning with a friendly message in a tooltip over the icon if you feel the need to tell more about what is happening to the user.
In my opinion, we don’t need to provide this feedback for most cases. We can revalidate without expecting the action of the user. SWR offers plentiful options for revalidating the data automatically.
My last cents
Try to be specific with the loader instead of a generalist. We can hold users’ anxiety by preparing the data to be loaded and being clear about what’s happening and what will happen on the page.
Avoid, as much as you can, to force the user to refresh the page. As a software engineer, you must provide the correct feedback at the right time by giving alternatives with scape actions in a stressful situation.
You can find all the code used in this example int this repository: https://github.com/toruticas/suspense-swr-skeleton.
Interested in working with us? We’re always looking for people passionate about technology to join our crew! You can check out our openings here.
*UPDATE*: If you are looking for an alternative to react-loading-skeleton with support to typescript and more robust, check the create-loader-content repo: https://danilowoz.com/create-content-loader/