Unblocking Transitions In React Components
A Tabbed Panel Component
Imagine a UI element with tab buttons that allow you to switch between different content.
You have tab buttons labeled A, B, and C to switch between contents. If you click tabB and quickly switch to either tab A or C, the UI might not respond. This is because when tab B is clicked, there’s a time-consuming “process” happening within the component TabContentB to display its content.
You experience this problem because React state updates are synchronous. If you examine the code, you’ll see that TabContentB renders 1,500 instances of a component named SlowComponent, which has a time-consuming loop.
Here’s a helpful resource to understand the synchronous and asynchronous nature of JavaScript.
Let’s write down the steps:
- When you click the tabB button, setTab(“b”) triggers a re-rendering of our app.
- The new tab will be “B”, and TabContentB will be displayed.
- However, the for-loop in TabContentB begins, and a
<SlowComponent/>
is pushed into the array. Keep in mind that our slow component takes 1 ms to complete its work. This step takes time and causes our app to block. The ongoing loop can’t be interrupted even if the user wants to initiate another interaction. Our interface becomes unresponsive. The loop proceeds to execute the remaining steps in the same manner. - Once the for-loop in TabContentB completes and a JSX array of 1,500 SlowComponent is generated, TabContentB finishes, and our app can be rendered with the new “b” tab state.
To address this issue, React provides a hook named useTransition that helps maintain a responsive UI by unblocking time-consuming tasks to allow user actions (like clicks) to continue seamlessly.
useTransition hook
useTransition allows us to mark state changes that we think might cause blocking and make our interface laggy, like the setTab setter function in our example, as non-critical. This enables the state changes to be interruptible. So, if the user decides to cancel and initiate another action, the ongoing operation can be stopped, and the new request can be executed.
This hook returns an array that has isPending as the first item and a function that you can use to mark any state update you want as non-critical.
Let’s refine our code with useTransition
We’ve updated our code with the startTransition function from the useTransition hook. Now, when you click the tab B button, it begins processing the time-consuming loop, but you can switch to other tabs without getting blocked.
This seems to solve the problem. However, there’s a new issue. If you click tab B and wait for its content to fully load, then try switching to another tab, there’s still a noticeable delay. This happens because useTransition ends up rendering TabContentB twice due to its approach to managing transitions.
Need for Memoization…
To mitigate the slowdown, we can use React’s memo function to wrap our slow component and avoid unnecessary re-renders unless the props change.
!!! --- This document is not yet complete. --- !!!