In React 18, the introduction of concurrent features has significantly changed the rendering of React applications. These features have a fundamental impact on improving the performance of your application. Let's step back and understand the basics of long tasks and performance measurements before upgrading to React 18.
Illustration depicting how Suspense in React 18 handles data fetching, rendering a fallback UI while waiting for data and resuming when ready.
Illustration depicting the main thread in a browser environment executing various tasks. Long tasks are marked, signifying their impact on performance.
When JavaScript code runs in the browser, it executes within a single-threaded environment known as the main thread. The main thread is responsible for executing JavaScript code, handling user interactions, processing network events, managing timers, updating animations, and managing browser reflows and repaints. It processes tasks one by one, which means that when a task is being executed, all other tasks must wait.
Long tasks are those that take longer than 50 milliseconds to complete. This benchmark is based on the requirement to generate a new frame every 16 milliseconds (60 frames per second) in order to maintain a fluid visual experience. The 50ms benchmark enables the device to dedicate resources to displaying frames and other duties like as responding to user input and executing JavaScript while still providing a smooth visual experience.
To achieve peak performance, keep the amount of long jobs to a minimum. Total Blocking Time (TBT) and Interaction to Next Paint (INP) are two significant measures for measuring the impact of long tasks on performance.
Graph showing Total Blocking Time (TBT) measurement
Graph representing the Interaction to Next Paint (INP) measurement, measuring the time from a user's first interaction with the page to the next paint.
Now that we have an understanding of long tasks and performance measurements, let's explore how React 18's concurrent features optimize application performance.
In traditional React rendering, visual updates occur in two phases: the render phase and the commit phase.
Visual representation of the traditional React rendering flow with a render phase and a commit phase.
React reconciles the old DOM with a new tree of React elements known as the virtual DOM during the render phase. It computes the differences between the existing DOM and the new React component tree and performs the required modifications. The commit phase follows the render phase and applies the adjustments to the actual DOM, creating, modifying, or deleting DOM nodes to match the new React component tree.
React grants equal priority to all elements in a component tree during a synchronous render. React renders a component tree as a single unbroken operation, whether on the first render or during a state change. This means that throughout the render phase, the main thread is blocked, resulting in an unusable UI until React completes the render and commits the result to the DOM.
In this synchronous rendering scenario, React developers would often use third-party libraries like debounce to defer rendering and reduce the impact of long tasks. However, React 18 introduces a new concurrent renderer that addresses these issues by prioritizing rendering tasks and providing a more responsive user experience.
Diagram illustrating the concurrent rendering process in React 18, with low-priority renders yielding to more important tasks.
React 18's parallel renderer works behind the scenes and includes methods for marking certain renders as non-urgent. When rendering low-priority components, React returns to the main thread every 5 milliseconds to check for more urgent tasks, such as user input or rendering another React component that is more relevant to the user experience at the time. React may make such renders non-blocking and prioritize more critical operations by continuously returning control to the main thread.
Furthermore, the concurrent renderer can "concurrently" render various versions of the component tree in the background without committing the result instantly. This implies that rendering can now be paused and resumed, allowing React to provide the best possible user experience. Based on user activity, React can pause the current render and prioritize rendering another update before continuing the previous render.
React offers a more fluid and responsive user experience by utilizing concurrent functionalities, particularly when dealing with high-frequency updates or CPU-intensive rendering operations.
React 18 introduces the Transitions API, which allows us to mark certain state updates as "transitions" to indicate that they can lead to visual changes that might disrupt the user experience if rendered synchronously. The startTransition function, provided by the useTransition hook, marks such updates as non-urgent.
By enclosing a state change in startTransition, we tell React that we're fine with postponing or pausing rendering in order to prioritize more critical activities and keep the UI interactive. This allows for smooth transitions between data requests or screen changes without interfering with user input.
1 import { useTransition } from "react"; 2 3 function Button() { 4 const [isPending, startTransition] = useTransition(); 5 6 return ( 7 <button onClick={() => { 8 urgentUpdate(); 9 startTransition(() => { 10 nonUrgentUpdate(); 11 }); 12 }}> 13 ... 14 </button> 15 ); 16 } 17 18 export default Button; 19
This feature is particularly useful in scenarios like the CitiesList demo mentioned earlier. By wrapping the state update in a startTransition, React can render the new tree in the background while keeping the current UI responsive to further user input. This significantly reduces the number of long tasks and improves overall performance.
Screenshot of a React Server Components demo showcasing server-side rendering and efficient client-side rendering without hydration.
React Server Components are an experimental feature in React 18 that enables rendering components on both the server and the client. This combines the interactivity of client-side apps with the performance benefits of traditional server rendering, without the cost of hydration.
Traditionally, React offered two primary ways to render applications: Client-Side Rendering (CSR) and Server-Side Rendering (SSR). CSR involves rendering everything on the client, while SSR renders the component tree to HTML on the server and sends it to the client to hydrate the components.
React Server Components allow sending the actual serialized component tree to the client, which the client-side React renderer can use to reconstruct the React component tree without requiring HTML or JavaScript bundles. This results in faster rendering and eliminates the need for separate API endpoints.
To use React Server Components, the renderToPipeableStream method from react-dom/server can be combined with the createRoot method from react-dom/client. By leveraging this new rendering pattern, React can achieve efficient server rendering and optimized client-side rendering without the need for hydration.
Another key concurrent feature in React 18 is suspense. Suspense was introduced in React 16 for code splitting with React.lazy, but with React 18 it is extended to data fetching.
We can use Suspense to delay a component's rendering until specific conditions are met, such as data being loaded from a remote source. Meanwhile, we can display a fallback component that indicates the loading status.
We avoid the requirement for conditional rendering logic by declaratively describing loading states with Suspense. Suspense integrates with React Server Components to provide direct access to server-side data sources without the need for separate API endpoints.
Suspense integrates deeply with React's Concurrent features. When a component is suspended, meaning it's waiting for data to load, React doesn't remain idle. It pauses the rendering of the suspended component and focuses on other tasks. During this time, React can render a fallback UI and prioritize other components based on user interaction.
Once the awaited data becomes available, React seamlessly resumes rendering the previously suspended component, providing a smooth user experience. Suspense, combined with React Server Component's streamable format, enables high-priority updates to be sent to the client as soon as they're ready, gradually revealing content in a non-blocking manner.
React 18 introduces a new API for efficient data fetching and memoization of results. The cache function allows remembering the result of a function call. If the same function is called with the same arguments within the same render pass, React uses the memoized value instead of re-executing the function.
1 import { cache } from 'react-query'; 2 3 export const getUser = cache(async (id: string) => { 4 const user = await db.user.findUnique({ id }); 5 return user; 6 }); 7
React 18 provides a caching technique by default for fetch calls, minimizing the amount of network requests in a single render pass. This caching feature improves application performance while lowering API expenses.
Because React Server Components cannot access the Context API, these data fetching functionalities are extremely useful. The automated caching feature of cache and fetch enables exporting and reusing a single function from a global module across the application, assuring efficient data fetching and memoization.
WiseGPT is an intelligent IDE plug-in available for popular code editors like VS Code and IntelliJ IDEA. Powered by generative AI, WiseGPT is designed specifically for developers to enhance their coding experience and optimize performance when working with data fetching. Here are some key features of WiseGPT:
WiseGPT leverages its vast knowledge and understanding of code patterns and best practices to generate performance-optimized data fetching code. Simply import a Postman collection, and WiseGPT will analyze the endpoints and automatically generate code snippets tailored to your specific needs.
WiseGPT goes beyond code generation by mirroring your coding style and structure. It analyzes your existing codebase and adapts to your preferred naming conventions, variable assignments, and overall coding style. This ensures that the generated code seamlessly fits into your project and maintains consistency.
To reduce technical debt and improve code maintainability, WiseGPT automatically adds meaningful comments throughout the generated code. These comments provide insights into the purpose of each code section, explain potential optimizations, and highlight any important considerations. With WiseGPT, understanding and maintaining the codebase becomes a breeze.
WiseGPT understands the complexities of large projects and handles code generation in multiple files seamlessly. It automatically splits the generated code into appropriate modules or files, ensuring that the output token limit is never exceeded. This feature allows you to work on extensive projects without any limitations.
WiseGPT excels in generating code for efficient data fetching. It leverages its deep understanding of data fetching patterns, such as caching, pagination, batching, and error handling, to provide optimized code snippets. These snippets help you avoid common pitfalls and adopt best practices in data fetching, ultimately enhancing the performance of your application.
With WiseGPT as your coding companion, you can unlock new levels of productivity and optimize your data fetching workflows. It combines the power of AI with your coding expertise, providing you with performance-focused code generation and insightful comments that reduce technical debt. Say goodbye to repetitive tasks and let WiseGPT streamline your coding experience.
In summary, upgrading to React 18 introduces several concurrent features that significantly improve the performance of React applications. With Concurrent React, rendering becomes a pauseable and resumable process, allowing the UI to remain responsive even during large rendering tasks.
The Transitions API enables smooth transitions during data fetches or screen changes, avoiding blocking user input. React Server Components combine the benefits of client-side interactivity and server rendering, resulting in improved performance without the cost of hydration.
Suspense provides a powerful way to handle asynchronous operations, allowing components to be rendered only when necessary and providing a smooth user experience. The data fetching and memoization features optimize network requests and ensure efficient rendering.
By leveraging these new features in React 18, developers can create high-performance applications with smooth user experiences and reduced blocking time.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.