React 18 introduced Concurrent Rendering, a feature that allows developers to create more responsive and faster user interfaces. With this feature, React can now work on multiple tasks simultaneously, making it possible to render large and complex components without blocking the main thread. This feature is especially useful for applications that require real-time updates and high interactivity allowing developers create better user experiences.
What is concurrent rendering?
Concurrent Rendering was a feature introduced in React 18 that improves the performance of React applications. It allows React to work on multiple tasks simultaneously, without blocking the main thread. Concurrency offers greater efficiency and speed for rendering large and complex components. This enables developers to produce more interactive applications with real-time updates, resulting in improved user experiences and enhanced performance.
How does concurrent rendering work?
Concurrent Rendering works by breaking down the rendering process into smaller tasks that can be executed independently. This is also starting to be seen in the new version of Nextjs. Where you can load or have state in one component while the rest of the page can load. React uses a priority-based scheduling algorithm to determine which task should be executed first. The algorithm then assigns priorities to each task based on its importance and urgency. Tasks with high priorities are executed first, while lower priority tasks are put on hold. Using this approach, React can ensure that the most important and urgent tasks are completed first, resulting in a more responsive and faster user interface. Not just that but, concurrent rendering also allows React to interrupt or abort a task if it becomes less important or urgent, further improving the performance of the application.
Examples of concurrent rendering
An example of this in React can be seen in a social media application that displays a user’s feed. When the user opens the app, the feed needs to be rendered, which involves fetching data, processing it, and rendering the individual posts. With concurrent rendering, React breaks down this process into smaller tasks.
For example, the rendering process could involve fetching the user’s data, fetching the feed data, and rendering each post. These tasks can be divided into smaller sub-tasks, such as fetching the user’s data separately from the feed data, and rendering each post independently.
React’s priority-based scheduling algorithm then assigns priorities to these tasks based on their importance and urgency. Fetching the user’s data might have a higher priority since it directly affects the user’s profile, while rendering each post may have a slightly lower priority.
Concurrent rendering allows React to execute these tasks independently, focusing on the higher priority ones first. This means that the user’s profile data could be fetched and displayed quickly, while the feed data and post rendering continue in the background. As a result, the user sees a more responsive interface, with the most important information being displayed promptly.
If, for some reason, a lower priority task becomes less important or urgent, such as the user scrolling past a particular post quickly, concurrent rendering allows React to interrupt or abort that task. This optimization further enhances the application’s performance by ensuring that unnecessary work is not performed, allowing resources to be allocated to more critical tasks.
Here’s an example that demonstrates Concurrent Rendering in React with code
import { unstable_concurrentAct as concurrentAct } from 'react-dom/test-utils'; function MyComponent() { const [userData, setUserData] = useState(null); const [feedData, setFeedData] = useState(null); useEffect(() => { concurrentAct(() => { fetchUserData().then((data) => { setUserData(data); }); fetchFeedData().then((data) => { setFeedData(data); }); }); }, []); return ( <div> {userData ? <UserProfile userData={userData} /> : <LoadingSpinner />} {feedData ? <Feed feedData={feedData} /> : <LoadingSpinner />} </div> ); } function fetchUserData() { return new Promise((resolve) => { setTimeout(() => { resolve({ name: 'John Doe', followers: 1000 }); }, 1000); }); } function fetchFeedData() { return new Promise((resolve) => { setTimeout(() => { resolve([ { id: 1, text: 'Hello world!' }, { id: 2, text: 'React is awesome!' }, { id: 3, text: 'Enjoying my day.' } ]); }, 2000); }); } function UserProfile({ userData }) { return ( <div> <h2>{userData.name}</h2> <p>Followers: {userData.followers}</p> </div> ); } function Feed({ feedData }) { return ( <ul> {feedData.map((post) => ( <li key={post.id}>{post.text}</li> ))} </ul> ); } function LoadingSpinner() { return <div>Loading...</div>; }
In this example, the MyComponent
component represents a part of a social media application that renders a user’s profile and their feed. The useState
hook is used to manage the state of userData
and feedData
, which initially start as null
.
The useEffect
hook is used to fetch the user data and feed data concurrently. By wrapping the fetch operations in the concurrentAct
function, React ensures that these tasks are executed concurrently, rather than blocking each other.
Once the data is fetched, the setUserData
and setFeedData
functions are called to update the state and trigger a re-render. This allows the components UserProfile
and Feed
to be rendered with the fetched data when it becomes available.
During the loading phase, a LoadingSpinner
component is rendered. Once the data is fetched, the UserProfile
and Feed
components are rendered with the respective data.
By breaking down the fetching and rendering tasks into smaller units and using Concurrent Rendering, React is able to prioritize and execute the tasks independently, resulting in a more responsive user interface where the user’s profile and feed are displayed as soon as they become available, without blocking each other.Impact on shared States
Sharing state can also have an impact on Concurrent Rendering in React. When multiple components share the same state, updates to that state can potentially trigger re-renders in all the components that depend on it. This can affect the concurrent execution of tasks and their priorities.
In concurrent rendering, React strives to execute tasks with higher priority first to ensure a more responsive user interface. However, if a shared state update occurs during the rendering process, it can lead to additional tasks being added to the queue, potentially affecting the priority order and execution sequence.
For example, let’s consider a scenario where two components, A and B, share a state variable called count
. Both components render based on the value of count
and perform some calculations or rendering logic accordingly. If an update is made to count
in component A, it may trigger a re-render of both component A and component B. During concurrent rendering, React will prioritize the rendering tasks based on their importance and urgency. However, the additional re-rendering task caused by the shared state update in component A might temporarily delay the execution of other tasks or alter their priority order.
React’s priority-based scheduling algorithm helps mitigate such issues by adapting the execution sequence dynamically. It aims to strike a balance between rendering tasks with high priority and ensuring fairness by allowing lower-priority tasks to also make progress.
Benefits of concurrent rendering
Faster and more responsive user interfaces
By using concurrent rendering, React can work on multiple tasks simultaneously, resulting in faster rendering times and a more responsive user interface.
Improved performance
Concurrent rendering allows React to prioritize and execute the most important and urgent tasks first, improving the overall performance of the application.
Real-time updates
Since concurrent rendering is particularly useful for applications that require real-time updates, developers can use this feature to create more dynamic and interactive user interfaces.
Better user experiences
By improving the performance and responsiveness of the user interface, this ultimately can help developers create better user experiences for their applications.
How to use concurrent rendering in React
As mentioned, concurrent rendering was implemented in version 18 of React. Once upgraded, you can start using this by breaking down the rendering process into smaller tasks and using the API to schedule these tasks. Developers can also use the new `<Suspense>` component to manage the loading of data and improve the user experience. Some best practices include prioritizing important tasks, minimizing the number of blocking operations, and testing the application performance under different conditions. By following these guidelines, developers can take full advantage of Concurrent Rendering and create faster, more responsive, and better performing React applications.
Best practices for using concurrent rendering
Prioritize important tasks
Assign priorities to each task based on their urgency and importance. This ensures that the most important tasks are completed first.
Minimize blocking operations
Reduce the number of blocking operations that can interfere with the rendering process. Use asynchronous operations and avoid long-running operations.
Test performance under different conditions
Test the performance of the application under different conditions to ensure that it performs well in all situations. Some ways to test could be using a performance profiler, this can help you identify the parts of your app that are taking time to render.
Using with server-side rendering (SSR).
Concurrent rendering primarily focuses on improving the performance and responsiveness of the client-side rendering process. However, it also has implications for server-side rendering.
Server-side rendering involves generating the initial HTML markup on the server and sending it to the client, allowing search engines and users with JavaScript-disabled browsers to see the content immediately. These concepts and techniques can be leveraged in the server-side rendering process to enhance its efficiency.
By following these best practices, developers can create faster, more responsive, and better-performing React applications.