JavaScript Development Space

React Performance: Using Modern Hooks for Smooth UI Interactions

Add to your RSS feed6 January 20254 min read
React Performance: Using Modern Hooks for Smooth UI Interactions

Modern web applications demand increasing interactivity, responsiveness, and speed. To meet these demands, the React team has developed tools that allow developers to finely control rendering and user experience. If you’re familiar with traditional optimization techniques such as useMemo, useCallback, memoization with React.memo, and other common strategies, the following hooks may intrigue you:

  • useTransition: Prioritizes rendering by splitting updates into critical and background tasks.
  • useDeferredValue: Defers updating heavy computations to prevent UI freezing during data input.
  • useOptimistic: Simplifies implementing optimistic updates out of the box.

This article explores the key concepts of these hooks with practical examples to demonstrate how and when to use them.

useTransition: Rendering Prioritization for Smooth UI

Concept

User actions like typing, switching tabs, or clicking buttons can trigger heavy state updates such as filtering large collections or recalculating complex data. If these updates are processed immediately with high priority, the UI may freeze momentarily, causing delays in user interaction.

Introduced in React 18, useTransition allows marking updates as “non-critical” or “transitionary.” This keeps the key UI interactions responsive while heavy updates are processed later in the background.

Basic Example

In the following example, text input updates the field instantly, ensuring responsiveness, while filtering a large list (filteredItems) is wrapped in startTransition, enabling React to process it in the background.

jsx
1 import React, { useState, useTransition } from 'react';
2
3 function BigList({ items }) {
4 return (
5 <ul>
6 {items.map((item) => (
7 <li key={item.id}>{item.text}</li>
8 ))}
9 </ul>
10 );
11 }
12
13 export default function App() {
14 const [text, setText] = useState('');
15 const [filteredItems, setFilteredItems] = useState([]);
16 const [isPending, startTransition] = useTransition();
17
18 const allItems = Array.from({ length: 10000 }, (_, i) => ({
19 id: i,
20 text: `Item ${i}`,
21 }));
22
23 const handleInputChange = (e) => {
24 const value = e.target.value;
25 setText(value);
26
27 startTransition(() => {
28 const filtered = allItems.filter((item) =>
29 item.text.toLowerCase().includes(value.toLowerCase())
30 );
31 setFilteredItems(filtered);
32 });
33 };
34
35 return (
36 <div>
37 <h1>List: {filteredItems.length} items</h1>
38 <input
39 value={text}
40 onChange={handleInputChange}
41 placeholder="Search the list..."
42 />
43 {isPending && <p>Loading...</p>}
44 <BigList items={filteredItems} />
45 </div>
46 );
47 }

How It Works

  • Responsive Input: The text state updates immediately, ensuring the input field remains responsive.
  • Background Updates: The filteredItems update is wrapped in startTransition, allowing React to process it later if the user continues typing.
  • Loading Indicator: The isPending state shows an indicator while the transition is in progress.

Use Cases for useTransition

  • Filtering/sorting large lists.
  • Redrawing complex components (e.g., maps with numerous objects).
  • Smooth animations during screen or tab transitions.

Caveats

  • useTransition doesn’t cancel computations; it adjusts priority.
  • For extremely heavy logic, consider additional optimizations like memoization or moving computations to a Web Worker.
  • Overusing useTransition may lead to noticeable delays in non-critical updates.

useDeferredValue: Lazy Updates for Heavy Data

Concept

useDeferredValue, introduced in React 18, is useful when you want dual states:

  • Immediate State: Updates immediately, ensuring a responsive UI.
  • Deferred State: Updates with lower priority, avoiding unnecessary re-renders during intensive tasks.

Example

Here, a text input updates instantly, while a deferred value is passed to a heavy component, minimizing UI lag.

jsx
1 import React, { useState, useDeferredValue, memo } from 'react';
2
3 const SearchResults = memo(function SearchResults({ searchTerm }) {
4 const allItems = Array.from({ length: 5000 }, (_, i) => `Item ${i}`);
5
6 const filteredItems = allItems.filter((item) =>
7 item.toLowerCase().includes(searchTerm.toLowerCase())
8 );
9
10 return (
11 <ul>
12 {filteredItems.map((item, idx) => (
13 <li key={idx}>{item}</li>
14 ))}
15 </ul>
16 );
17 });
18
19 export default function App() {
20 const [inputValue, setInputValue] = useState('');
21 const deferredValue = useDeferredValue(inputValue);
22
23 return (
24 <div>
25 <input
26 value={inputValue}
27 onChange={(e) => setInputValue(e.target.value)}
28 placeholder="Search..."
29 />
30 <SearchResults searchTerm={deferredValue} />
31 </div>
32 );
33 }

How It Works

  • inputValue updates instantly, keeping the input field responsive.
  • deferredValue lags behind, allowing React to optimize rendering.

Differences from useDebounce

  • useDebounce: Adds a time-based delay before state updates.
  • useDeferredValue: Dynamically adjusts rendering priority without explicit time delays.

useOptimistic: Simplifying Optimistic Updates

Concept

Optimistic updates immediately reflect user actions in the UI while the server processes the changes. If the server fails, the UI reverts to its original state. React 19’s useOptimistic simplifies this process.

Example

In this example, new orders are optimistically added to the list, giving users instant feedback.

jsx
1 import { useOptimistic, useState, useRef } from 'react';
2
3 async function makeOrder(orderName) {
4 await new Promise((res) => setTimeout(res, 1500));
5 return orderName;
6 }
7
8 function Kitchen({ orders, onMakeOrder }) {
9 const formRef = useRef();
10
11 const [optimisticOrders, addOptimisticOrder] = useOptimistic(
12 orders,
13 (state, newOrder) => [...state, { orderName: newOrder, preparing: true }]
14 );
15
16 async function formAction(formData) {
17 const orderName = formData.get("orderName");
18 addOptimisticOrder(orderName);
19 formRef.current.reset();
20 await onMakeOrder(orderName);
21 }
22
23 return (
24 <div>
25 <form action={formAction} ref={formRef}>
26 <input type="text" name="orderName" placeholder="Enter order" />
27 <button type="submit">Order</button>
28 </form>
29 {optimisticOrders.map((order, index) => (
30 <div key={index}>
31 {order.orderName} {order.preparing ? '(Preparing...)' : '(Ready!)'}
32 </div>
33 ))}
34 </div>
35 );
36 }
37
38 export default function App() {
39 const [orders, setOrders] = useState([]);
40
41 async function onMakeOrder(orderName) {
42 const sentOrder = await makeOrder(orderName);
43 setOrders((orders) => [...orders, { orderName: sentOrder }]);
44 }
45
46 return <Kitchen orders={orders} onMakeOrder={onMakeOrder} />;
47 }

Advantages

  • Simplifies optimistic state management.
  • Built-in rollback mechanism for server errors.

Use Cases

  • Instant feedback for comments, likes, or messages.
  • Adding items to a shopping cart.

Conclusion

  • useTransition: Marks non-critical updates for background processing.
  • useDeferredValue: Defers rendering heavy computations for smoother UI.
  • useOptimistic: Simplifies optimistic UI updates with rollback capability.

Understanding and using these hooks effectively can significantly enhance the responsiveness of your React applications.

JavaScript Development Space

© 2025 JavaScript Development Space - Master JS and NodeJS. All rights reserved.