Rendering Revamped: A Look at the Differences Between React 17 and 18...and the Suspense Data Fetching Dilemma
The differences in how rendering works between React 17 and 18. Solving the data fetching challenge with React Suspense.
In this newsletter issue, I will explain how the rendering of React components radically changes from 17 to 18. We will explore the pros and cons for each approach, and some production examples I had to deal with.
Client-Side Rendering in React 17
In React 17, client-side rendering is the default and most commonly used approach. This means that the components are rendered on the user's browser, using the virtual DOM (Document Object Model) to update the UI with changes in data.
When a user visits a website or a web application built with React 17, the server sends the initial HTML, along with the JavaScript code for the React components. The browser then executes the code and renders the components, allowing the user to interact with the UI.
In practice, it works this way. The browser fetches and loads everything (HTML + JS). Then, it hydrates all the DOM. Eventually, it shows everything on the screen.
One of the main benefits of client-side rendering is that it allows for fast updates and interactive experiences. The components can be easily re-rendered on the client without requiring a full page refresh. It also allows for the use of client-side state and APIs, which are not available on the server.
However, client-side rendering can also have drawbacks. It can cause slower initial load times. The browser must first download and execute the JavaScript before rendering the components. This can be particularly problematic for users on slow connections.
Server-Side Rendering in React 18
React 18 introduces the option of server-side rendering, or SSR. This means that the initial rendering of the components is done on the server, instead of the client.
When a user visits a website or web application built with React 18 and server-side rendering, the server will generate the HTML for the initial render of the components and send it to the client. The client then "hydrates" the pre-rendered HTML with the Javascript code, matching the references.
It means that the JS will become available in a second moment when it will be loaded and attached to the various elements of the DOM. Also, if the code is supposed to modify the DOM, the affected components will be re-rendered accordingly.
One of the main benefits of server-side rendering is improved initial load times. The server can send the fully rendered HTML to the client, allowing it to be displayed immediately.
Another advantage is that it's possible to use SSR with various new APIs. For instance, we can combine it with Suspense. We can use Suspense to customize the loader to show the user or the order of appearance of the various components on a page. Or we can even continue to work on a page while the new page is loading with the useTransition API.
However, server-side rendering can also have drawbacks. It can be more complex to implement. It can also cause issues with components that rely on APIs, as the initial rendering on the server does not have access to these. Suspense does not work properly with data fetching.
Data Fetching Issues in React 18 SSR with Suspense
One of the issues with data fetching using React 18 SSR with Suspense is that Suspense is not supported on the server, as it relies on client-side states and APIs. This means that any components using Suspense for data fetching will not be able to render their initial state on the server, causing the server-rendered HTML to be incomplete.
To solve this issue, one must use an alternative data fetching approach. The React team said that SSR with data fetching was not made thinking about a common Create React App. Their suggestion is to use a framework that implements this out of the box, like NextJS, Remix, or others.
But of course, in a work-related project, we can't just change the whole structure of an application.
A production example
For example, on a production case, I had a project using React 17. A page was fetching data from the backend and then displaying it. Meanwhile, the page was loading and there was a spinner.
When I upgraded the application to React 18, the default HTML page was immediately shown to the user. In the background, the data was being fetched. Once the promise was successful and the data was pulled, the whole page would refresh and show a different screen (the correct one).
How to solve this? It was quite complex to understand how to go around SSR to create a correct result.
Here's my solution. Suspense is used with the routing of the whole application, so I couldn't skip it. So I created a condition for the rendering of the page component.
I conditioned the component to render to a state, which was true if the data was present. Otherwise, it would load the spinner.
In the first HTML rendering, the condition is not met and the state is false, so the loader starts. Then, when the data is fetched, the component could render with all the data and the correct hydrated HTML.
So when is Suspense useful?
Suspense becomes extremely useful in two cases.
The first is when the users have a slow internet connection or devices. As Suspense awaits the JS to be loaded, it shows directly a fallback component (which can be a loader page or spinner) instead of leaving the user with a blank page.
In the second case, if we have different components we can customize Suspense to be used only in data-heavy parts of the application. Effectively, we can show almost the whole page to the user, except for the loading parts which will be deferred.
Will Suspense issues with data fetching be solved? (…hello use hook?)
Future versions of React will likely address the issues with data fetching using Suspense in SSR. One of the topics in discussions these days is a new hook called “use". You can read about it at this link.
To sum up, “this (hook) enables React developers to access arbitrary asynchronous data sources with Suspense via a stable API."
So, we'll have to wait for it to become available. For now, they're still solving the cache implementation on this new hook.
And in the meanwhile, let's (us that are using still CRA) all do workarounds and hacks for our apps!
Other articles I posted
Finding a bug after writing a test, an example
Let's connect on Twitter or Mastodon!