ReactJS - useDeferredValue Hook



The version of React 18 included a number of new React hooks that help with concurrency and rendering slow content. The useDeferredValue hook is one of those hooks that is simple to use but tough to understand. In this tutorial, we will see how this hook works so we know how and when to use it.

Working of useDeferredValue hook

In React, the useDeferredValue hook is used to interact with concurrent mode. Concurrent mode is an experimental React feature that allows us to schedule and prioritise rendering updates to create more responsive and dynamic user interfaces.

useDeferredValue is commonly used to defer the processing of certain values when they are required. This can help to minimise the amount of work done in a given render cycle and enhance our application's performance. This hook is very useful in cases when we have values that are not immediately required, such as network queries or large calculations.

We can call ‘useDeferredValue’ at the top level of our component to get a deferred version of the value.

Syntax

const deferredValue = useDeferredValue(value);

Parameter

value − It is the value we want to defer. It is typically a piece of data or a variable that can not be immediately needed or can be processed at a later time.

Return Value

The returned delayed value will be the same as the value we gave during the original render. During updates, React will first try a re-render with the old value, and then another re-render in the background with the new value.

We can use this hook in three ways. First by deferring re-rendering for a portion of the user interface, secondly by showing that the content has become old and third by showing old content while new content is loading.

Examples

So we will discuss all these three ways to use the useDeferredValue hook.

Example − Deferring re-rendering for a portion of the user interface(UI)

In a React application, we can face a situation in which a portion of our UI is slow to update and cannot make it faster. Assume that we have a text input field, and every time we write a letter, a component (such as a chart or a long list) refreshes or re-renders. This frequent re-rendering might slow down our app, even for simple operations like typing.

It's possible to use the useDeferredValue hook to avoid this slow component from affecting the rest of the UI.

import React, { useState } from 'react';
import { useDeferredValue } from 'react';

function SlowList({ text }) {
   
   // Slow operation like rendering a long list
   const slowListRendering = (text) => {
      console.log(`Rendering the list for text: ${text}`);
      setTimeout(() => {
         console.log('List rendering complete.');
      }, 1000);
   };
   
   slowListRendering(text);
   
   return (
      <div>
         <p>This is a slow component.</p>
         {/* Render a chart here */}
      </div>
   );
}

function App() {
   const [text, setText] = useState('');
   const deferredText = useDeferredValue(text); // Defer the text input value
   
   return (
      <>
         <input value={text} onChange={(e) => setText(e.target.value)} />
         <SlowList text={deferredText} /> {/* Pass the deferred value */}
      </>
   );
}
export default App;

Output

slow component

Example − Showing that the content has become old

When using useDeferredValue, it is necessary to show that the content is old in order to inform the user that the data can not be the most up-to-date due to the deferral.

In the below example, the isStale variable is calculated by comparing the deferredData to the current data. If they don't match, it means the material is out of date, and the UI shows a "Updating..." message to let the user know that the data is being updated. When using useDeferredValue, this is an easy way to deliver feedback regarding data staleness.

import React, { useState } from 'react';
import { useDeferredValue } from 'react';

function MyComponent({ deferredData }) {
   
   // Slow operation
   function slowOperation(data) {
      return new Promise((resolve) => {
         setTimeout(() => {
            resolve(`Processed data: ${data}`);
         }, 2000);
      });
   }
   
   const [data, setData] = useState('Initial data'); // Initialize data
   const isStale = deferredData !== data;
   
   if (isStale) {
   
      // Data is old, simulate loading
      slowOperation(deferredData).then((result) => {
         setData(result); // Update data when it's no longer old
      });
   }
   
   return (
      <div>
         <p>Data: {data}</p>
         {isStale && <p>Updating...</p>}
      </div>
   );
}

function App() {
   const [inputData, setInputData] = useState('');
   const deferredInputData = useDeferredValue(inputData);
   
   return (
      <div>
         <input
         type="text"
         value={inputData}
         onChange={(e) => setInputData(e.target.value)}
         />
         <MyComponent deferredData={deferredInputData} />
      </div>
   );
}
export default App;

Output

initial data

Example − Showing old content while new content loads

We can use useDeferredValue to preserve both the old and new data states and conditional render them based on the staleness of the data to show old content while new content loads. Here's an example to demonstrate −

data.js

let cache = new Map();

export function fetchData(url) {
   if (!cache.has(url)) {
      cache.set(url, getData(url));
   }
   return cache.get(url);
}

async function getData(url) {
   if (url.startsWith("/search?q=")) {
      return await getSearchResults(url.slice("/search?q=".length));
   } else {
      throw Error("Not Found");
   }
}

async function getSearchResults(query) {

   // Delay to make waiting
   await new Promise((resolve) => {
      setTimeout(resolve, 400);
   });
   
   const allData = [
      {
         id: 1,
         title: "ABC",
         year: 2000
      },
      {
         id: 2,
         title: "DEF",
         year: 2001
      },
      {
         id: 3,
         title: "GHI",
         year: 2002
      },
      {
         id: 4,
         title: "JKL",
         year: 2003
      },
      {
         id: 5,
         title: "MNO",
         year: 2004
      },
      {
         id: 6,
         title: "PQR",
         year: 2005
      },
      {
         id: 7,
         title: "STU",
         year: 2006
      },
      {
         id: 8,
         title: "VWX",
         year: 2007
      },
      {
         id: 9,
         title: "YZ",
         year: 2008
      }
   ];
   
   const lowerQuery = query.trim().toLowerCase();
   return allData.filter((data) => {
      const lowerTitle = data.title.toLowerCase();
      return (
         lowerTitle.startsWith(lowerQuery) ||
         lowerTitle.indexOf(" " + lowerQuery) !== -1
      );
   });
}

SearchData.js

import { fetchData } from "./data.js";

export default function SearchData({ query }) {
   if (query === "") {
      return null;
   }
   const myData = use(fetchData(`/search?q=${query}`));
   if (myData.length === 0) {
      return (
         <p>
            No data found for <i>"{query}"</i>
         </p>
      );
   }
   return (
      <ul>
         {myData.map((data) => (
            <li key={data.id}>
            {data.title} ({data.year})
            </li>
         ))}
      </ul>
   );
}

function use(promise) {
   if (promise.status === "fulfilled") {
      return promise.value;
   } else if (promise.status === "rejected") {
      throw promise.reason;
   } else if (promise.status ===    "pending") {
      throw promise;
   } else {
      promise.status = "pending";
      promise.then(
         (result) => {
            promise.status = "fulfilled";
            promise.value = result;
         },
         (reason) => {
            promise.status = "rejected";
            promise.reason = reason;
         }
      );
      throw promise;
   }
}

App.js

import { Suspense, useState } from 'react';
import SearchData from './SearchData.js';

export default function App() {
   const [query, setQuery] = useState('');
   return (
      <>
         <label>
            Search for the Data here:
            <input value={query} onChange={e => setQuery(e.target.value)} />
         </label>
         <Suspense fallback={<h2>Data is Loading...</h2>}>
            <SearchData query={query} />
         </Suspense>
      </>
   );
}

Output

search for data

Limitations

In React, the useDeferredValue hook has a few limitation that can be defined in a few words −

  • Some older web browsers may not completely support the useDeferredValue hook. As a result, if we need to support outdated browsers, we may have difficulties.

  • Using useDeferredValue might complicate our code, when dealing with multiple deferred values. This additional complexity can make our code more difficult to understand and maintain.

  • While useDeferredValue can help with performance, it's not the best solution for all performance issues. We still need to consider other performance optimizations like code splitting and server-side rendering for better results.

  • If the developer is new to React, it may require effort and some time to understand the useDeferredValue hook.

reactjs_reference_api.htm
Advertisements