This results in improved initial load times as the server can send the fully rendered HTML to the client, allowing it to be displayed immediately. It also allows the use of new APIs such as Suspense and useTransition. However, it can also be more complex to implement and may cause issues with components that rely on APIs, as the initial rendering on the server does not have access to these.
Challenges and Solutions for Using React 18's Server-Side Rendering with Suspense for Data Fetching
Using React 18's SSR with Suspense can pose challenges when it comes to data fetching. Since Suspense relies on client-side states and APIs, it is not supported on the server, resulting in incomplete server-rendered HTML for components that utilize Suspense for data fetching. To address this issue, an alternative data fetching approach must be used. The React team has acknowledged that SSR with data fetching was not designed for use in common Create React App projects and recommends using a framework like NextJS or Remix that implements this feature out of the box. However, in a work-related project, it may not be feasible to overhaul the entire application structure.
An example from a production application
In one of my recent projects, I encountered a challenge when upgrading from React 17 to React 18. The issue arose when a page in the application was fetching data from the backend while also displaying a loading spinner. After the upgrade, the default HTML page was displayed to the user before the data was finished being fetched, causing the entire page to refresh and display the correct information only afterward.
To solve this problem, I had to come up with a solution that worked within the constraints of server-side rendering (SSR) and the routing of the entire application. My solution was to create a condition for the rendering of the page component. I set the component to only render when the data was present and displayed a loading spinner if the data was not yet available. This way, during the initial HTML rendering, the condition is not met and the state is false, causing the loading spinner to be displayed. Once the data is fetched, the component can then render with all the necessary information and the correct hydrated HTML.
Enhancing Slow Connections and Data-Heavy Applications with Suspense:
Suspense is a powerful tool that can improve the user experience in certain situations.
Secondly, Suspense can also be used to optimize data-heavy parts of the application. By customizing Suspense, it can be applied only to specific components, allowing for almost the entire page to be displayed to the user, except for the loading parts which will be deferred.
Navigating Data Fetching in React: The Upcoming use Hook
It is expected that future versions of React will address the challenges currently faced with utilizing Suspense for server-side rendering (SSR) and data fetching. One solution that is currently being discussed is the introduction of a new hook called "use". This hook will allow developers to access asynchronous data sources with Suspense through a stable API. You can read about it at this link. While this solution is not yet available, the React team is still working on implementing the cache feature for this hook. In the meantime, those using Create React App (CRA) may need to find a workaround and hacks to handle data fetching in their applications.
Enhancing User Experience with React 18's useTransition Hook: Implementing Seamless Transitions in Your Application
In order to create a seamless and enjoyable experience for users, it is important to pay attention to the visual effects when transitioning between pages or components in an application. React 18 has introduced a new API called useTransition, which allows developers to easily implement transitions. The useTransition hook returns a tuple containing two objects: isPending, which indicates whether the transition is completed, and startTransition, a method that can be called to initiate the transition.
This hook can be particularly useful when used in combination with Suspense, as it can provide a smooth experience for users while new components or pages are being loaded. For example, if a method is used to handle a click to change a tab on a page, the transition can be started there and the isPending boolean can be used as a loading state for the component.
Exploring the Differences between Manual Loading in React 17 and Suspense and useTransition in React 18
I’m providing two code sandboxes for you to compare the methods of loading pages and states in React. The first sandbox uses the traditional methods in React 17, while the second uses the new Suspense and useTransition hook in React 18.
The first code sandbox demonstrates manual loading with React 17: react-17-loading-content-switch-tabs - CodeSandbox
The second code sandbox shows how Suspense and useTransition can be used in React 18: react-18-loading-content-suspense-useTransition - CodeSandbox
Both sandboxes simulate a slow API request using setTimeout.
The React 17 sandbox demonstrates manual loading and the use of a loading state, which can lead to delays for users when switching between tabs. In contrast, the React 18 sandbox uses the useTransition hook to allow for a more seamless user experience while new content is being loaded. The loading process is indicated by a progressively coloring top bar, which is less disruptive to the user.
It's worth noting that while Suspense can be useful for loading components, it doesn't work well with data fetching in a vanilla React application. Therefore, the React 18 sandbox also includes a normal loader for data fetching as well as a global loader that uses setTimeout to trigger Suspense while the component is loading, displaying the colored top bar.
Understanding the New Data Rendering Approaches: fetch-as-render, render-then-fetch
Let’s take a look at the new approaches for data rendering in React 18, with the introduction of Server-Side Rendering and Suspense. Two new approaches, "fetch-as-render" and "render-then-fetch," are now available, which differ from the traditional "fetch-then-render" approach that was used in React 17.
Both of these new methods allow for rendering to occur simultaneously with data fetching, rather than in a sequential manner as was previously the case. This allows for a better user experience, as data is fetched in the background while the user is interacting with the page.
To illustrate the difference between these approaches, consider the following image:
In the traditional "fetch-then-render" approach, data fetching occurs sequentially, one API call after another. With the "fetch-as-render" and "render-then-fetch" approaches, multiple API calls can be made simultaneously, while the HTML can be already rendered, resulting in a more efficient overall process.
Moreover, it’s also possible to customize the loading of each component using different Suspense(s) in a SuspenseList:
Overcoming Common Challenges: Solutions to Common Issues
Migrating to React 18 can be challenging, especially when it comes to implementing new features correctly. It's crucial to understand how React 18 updates will impact your application and to plan and test thoroughly to avoid problems. Here are some common issues and their potential fixes:
- Automatic Batching:
- Problem: In some cases, automatic batching can combine state updates, leading to conflicts with third-party libraries or external stores.
- Solution: Store state elsewhere or switch from useState to useRef to prevent component re-rendering.
- Multiple Mounting:
- Problem: In development mode with strict mode on, components may render twice due to useEffect without dependencies. This is a result of the new strict mode cycle: mount → render → unmount → mount → render.
- Solution: Temporarily, wrap the useEffect code inside a useRef condition to run the code only once. In production, the component will mount and render once as expected.
- Suspense not working with data fetching:
- Problem: React developers suggest using frameworks like NextJS or libraries like react-query instead of Create React App for seamless Suspense functionality.
- Solution: With CRA, workarounds include moving states, adding loaders for data fetching, or using useTransition. The upcoming "use" hook may also provide a solution.
React is becoming increasingly complex with each version, leading to tricky bugs. For instance:
- Libraries like Formik can create issues with React 18, and the solution is to switch libraries.
- With React, it's crucial to optimize every component with hooks like memo, useMemo, and useCallback to prevent any issues. Other frameworks like Vue and SvelteKit have these features built-in.
Sometimes those breaking changes will force you to change completely the structure of the project because of one thing not working.
The increasing layers of complexity in React are interesting, but tiring at the same time. Bugs happen and it's difficult to understand why. Especially for somebody starting to learn it just now.
In conclusion, React 18 introduces several new features that can enhance the user experience in your applications. However, it is important to be aware of the potential issues that may arise with an incorrect implementation of React 18, such as automatic batching and multiple mounting, and to consider potential solutions for these issues.