Education
Developer Advocate
Last updated on Oct 31, 2023
Last updated on Sep 11, 2023
React is a very flexible library that provides us with a number of different hooks one can use to manage application state. One of these hooks is the useTransition hook. This hook is used to control the timing of state changes that could occur based on user interactions. Simply put, useTransition hook enables state updates to occur in a way that is less blocking for user inputs resulting in a smoother user experience.
To understand how the useTransition hook works, let's take a look at an example. Consider a scenario where the user interacts with an input box. When they start typing, the application initiates a search operation that could potentially cause a slow state update.
Here is a simple example of how the useTransition hook can be used in React:
1 import React, { useState, useTransition } from 'react'; 2 3 function App() { 4 const [startTransition, isPending] = useTransition({ timeoutMs: 3000 }); 5 const [value, setValue] = useState(''); 6 7 const onInputChange = (event) => { 8 startTransition(() => { 9 setValue(event.target.value) 10 }); 11 } 12 13 return ( 14 <div className="App"> 15 <input 16 value={value} 17 onChange={onInputChange} 18 placeholder='Start typing...' 19 /> 20 </div> 21 ); 22 } 23 24 export default App; 25
The useTransition hook when invoked returns two values: startTransition and isPending, both are important for managing state updates. The function startTransition marks the state update as an "update that can wait" or low priority. The isPending variable indicates whether a transition is currently running, providing a perfect scenario to display a loading indicator to the user.
Transition updates made inside the startTransition function contain the state changes that may be interrupted by more urgent updates. For example, if a user types into an input field, the derived state — updated by less critical state updates — would not cause the textbox to block and the urgent render, a result of the user input, would update the textbox immediately.
On the other hand, it is the callback function inside the startTransition function that houses the less urgent state updates. The isPending variable provides valuable feedback about these updates and their current status. For instance, through the isPending variable, slower devices can provide a graceful indication to the user that work is being performed in the background. This helps provide smoother transitions on older devices.
The state updates in React apps are almost always scheduled asynchronously, and they might not always be urgent. When the state updates aren't urgent, they provide us an opportunity to perform some optimizations. Here, the useTransition hook comes to our rescue.
A common example is handling input from user interactions, as is often seen with an input field. When the user interacts with an application by inputting data into these fields, there are times when many state updates could be triggered. This might even lead to performance issues.
1 import React, { useState, useTransition } from 'react'; 2 3 function App() { 4 const [startTransition, isPending] = useTransition(); 5 const [inputValue, setInputValue] = useState(''); 6 7 const handleInputChange = (event) => { 8 const newInputValue = event.target.value; 9 startTransition(() => { 10 setInputValue(newInputValue); 11 }); 12 }; 13 14 return ( 15 <div className="App"> 16 <input 17 type="text" 18 value={inputValue} 19 onChange={handleInputChange} 20 placeholder="Type here..." 21 /> 22 {isPending && <p>Update in progress...</p>} 23 </div> 24 ); 25 } 26 27 export default App; 28
The startTransition function effectively marks the state update inside it as lower priority. The isPending variable holds whether there are any pending state updates and could be used to provide user feedback.
One of the benefits of using useTransition is that it empowers developers to categorize and manage state updates based on urgency. Some updates are urgent and need to take effect immediately. For instance, the user input in a text box must be shown as soon as possible. Other state updates are not as critical.
While handling slow state updates, we can put the corresponding state variable change in the callback function of startTransition, which is designed to contain less urgent updates. As soon as the startTransition function gets called, it establishes a moment of synchrony for the code inside, meaning all the updates will be batched together.
By using useTransition hook and understanding how the hook works, developers can provide a smoother and more responsive user experience reducing the perceived lag in user interactions. Therefore, improving the overall performance of an application becomes eminently viable with fine-grained control of state updates guided by useTransition.
useTransition becomes exceptionally handy in handling Input Churn scenarios – a scenario where a state update is too slow that immediately succeeding input causes the update to get dismissed. This can happen when a user types faster than the application can handle. At such moments, user interactions can outpace the application's state updates, and that might not yield an ideal user experience.
1 import React, { useState, useTransition } from 'react'; 2 3 function App() { 4 const [startTransition, isPending] = useTransition(); 5 const [inputValue, setInputValue] = useState(''); 6 7 const handleInputChange = (event) => { 8 startTransition(() => { 9 setInputValue(event.target.value); 10 }); 11 }; 12 13 return ( 14 <div className="App"> 15 <input 16 type="text" 17 value={inputValue} 18 onChange={handleInputChange} 19 placeholder="Type here..." 20 /> 21 {isPending && <p>Loading...</p>} 22 </div> 23 ); 24 } 25 26 export default App; 27
In this example, if a user types something in the input box and if the component has a longer render time, the useTransition hook ensures that the slow state update does not cause any lag in the user interaction. It does so by lowering the priority of the state update, thereby pushing the re-render caused by this state update to the end of the task queue.
React strives to make our applications responsive to user interactions and aims to maximize the throughput of these interactions. Indeed, while dealing with performance issues, we often confront scenarios where certain updates are less significant and can be deferred slightly. That's exactly what useTransition allows.
Most importantly, with the useTransition hook, it's possible to avoid unnecessary renders and performance hits that might arise from updating the state too often or too quickly. So, as the user interacts with the application code, it ensures that these updates don't block critical rendering tasks. Thanks to the useTransition hook, it is now plausible to attain smooth transitions even under heavy load.
With the final official non-beta release of React 17, the useTransition hook is more polished and now supports more control by delaying the state update. However, it's worth mentioning that using useTransition does not guarantee the update to appear immediately. Its fundamental purpose is to manage the timing of an update when user interaction is going on.
The following code demonstrates a simple implementation of useTransition in React version 17.
1 import React, { useState, useTransition } from 'react'; 2 3 function App() { 4 const [resource, setResource] = useState(initialResource); 5 const [startTransition, isPending] = useTransition({ 6 timeoutMs: 3000, 7 }); 8 9 return ( 10 <> 11 <Suspense fallback={<h1>Loading profile...</h1>}> 12 <Profile resource={resource} /> 13 </Suspense> 14 <button 15 disabled={isPending} 16 onClick={() => { 17 startTransition(() => { 18 const nextUserId = getNextId(resource.userId); 19 setResource(fetchProfileData(nextUserId)); 20 }); 21 }} 22 > 23 Next 24 </button> 25 </> 26 ); 27 } 28 export default App; 29
react-dom ensures that Inputs are handled within the same event. This commitment ensures user inputs are responsive all the time, avoiding unnecessary delays.
useTransition is a powerful tool that eases the control of state updates, particularly in relation to user interactions. The strength of this hook comes from the ability to demarcate low-priority state updates, allowing the program to proceed with more urgent updates. This flexibility brings about a considerable improvement in application responsiveness and overall user experience.
Being a feature of React 17 and subsequent versions, mastering useTransition and other react hooks could, therefore, be seen as a step to modernize and equip one's self with the requisite skills to build highly responsive and efficient apps. By continually evolving with these updates, React continues to present developers with exciting new possibilities that can visibly enhance the functionality of their applications.
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.