How Does the useDeferredValue Hook Work in React?
How Does the useDeferredValue Hook Work in React?

How Does the useDeferredValue Hook Work in React?

React now has concurrency support with the release of version 18. There are numerous features now that help to make better use of system resources and boost app performance. One such feature is the?useDefferedValue?hook, in this article we're going to learn about?useDeferredValue?and understand the scenarios where we can use it.

Why do we need?useDefferedValue?

Before we can see this hook in action, we need to understand something about how React manages state and updates the DOM.

Let’s say we have the following code:

App.js

export default function App() {
  const [name, setName] = useState('');
  const computedValue = useMemo(() => {
    return getComputedValue(name);
  }, [name]);
  const handleChange = (event) => {
    setName(event.target.value);
  };
  return (
    <input
      type="text"
      placeholder="Username"
      value={name}
      onChange={handleChange}
    />
  );
}        

Here we create a state variable with the?useState ?hook, and a computed value (computedValue) derived from the state. We use the?useMemo ?hook to recalculate the computed value only when the state changes.

So when the value of the input field changes, the?name?state variable is updated and the computed value is recomputed before the DOM is updated.

This usually isn’t an issue, but sometimes this recalculation process involves a large amount of computation and takes a long time to finish executing. This can reduce performance and degrade the user experience.

For example, we could be developing a feature that lets a user search for an item in a gigantic list:

App.js

function App() {
  const [query, setQuery] = useState('');
  const list = useMemo(() => {
    // ?? Filtering through large list impacts performance
    return largeList.filter((item) => item.name.includes(query));
  }, [query]);
  const handleChange = (event) => {
    setQuery(event.target.value);
  };
  return (
    <>
      <input type="text" value={query} onChange={handleChange} placeholder="Search"/>
      {list.map((item) => (
        <SearchResultItem key={item.id} item={item} />
      ))}
    </>
  );
}        

In this example, we have a query state variable used to filter through a huge list of items. The longer the list, the more time it will take for the filtering to finish and the?list?variable to be updated for the DOM update to complete.

So when the user types something in the input field, the filtering will cause a delay in the DOM update and it’ll take time for the text in the input to reflect what the user typed immediately. This slow feedback will have a negative effect on how responsive your app feels to your users.

I simulated the slowness in the demo below so you can better understand this problem. There are only a few search results for you to properly visualize it, and they’re each just the uppercase of whatever was typed into the input field.

In this demo, I am typing each character one after the other as quickly as I can, but because of the artificial slowness, it takes about a second for my keystroke to change the input text.

useDeferredValue?in action

This is a situation where the?useDeferredValue?hook is handy.?useDeferredValue()?accepts a state value as an argument and returns a copy of the value that will be deferred, i.e., when the state value is updated, the copy will not update accordingly until after the DOM has been updated to reflect the state change. This ensures that urgent updates happen and are not delayed by less critical, time-consuming ones.


function App() {
  const [query, setQuery] = useState('');
  // ?? useDefferedValue
  const deferredQuery = useDefferedValue(query);
  const list = useMemo(() => {
    return largeList.filter((item) => item.name.includes(query));
  }, [deferredQuery]);
  const handleChange = (event) => {
    setQuery(event.target.value);
  };
  return (
    <>
      <input type="text" value={query} onChange={handleChange} placeholder="Search" />
      {list.map((item) => (
        <SearchResultItem key={item.id} item={item} />
      ))}
    </>
  );
}        

In the example above, our previous code has been modified to use the?useDeferredValue?hook. As before, the?query?state variable will be updated when the user types, but this time,?useMemo?won't be invoked right away to filter the large list, because now?deferredQuery?is the dependency?useMemo?is watching for changes, and?useDeferredValue?ensures that?deferredQuery?will not be updated until after?query?has been updated and the component has been re-rendered.

Since?useMemo?won't be called and hold up the DOM update from the change in the?query?state, the UI will be updated without delay and the input text will change once the user types. This solves the responsiveness issue.

After the?query?state is updated,?then?deferredQuery?will be updated, causing?useMemo?to filter through the large list and recompute a value for the?list?variable, updating the list of items shown below the input field.

As you can see in the demo, the text changes immediately as I type, but the list lags behind and updates sometime later.

If we keep changing the input field’s text in a short period (e.g., by typing fast), the?deferredQuery?state will remain unchanged and the list will not be updated. This is because the?query?state will keep changing before?useDeferredValue?can be updated, so?useDeferredValue?will continue to delay the update until it has time to set?deferredQuery?to the latest value of?query?and update the list.

Here’s what I mean:

This is quite similar to debouncing, as the list is not updated till a while after input has stopped.

Tip

Sometimes in our apps, we’ll want to perform an expensive action when an event occurs. If this event happens multiple times in a short period, the action will be done as many times, decreasing performance. To solve this, we can set a requirement that the action only be carried out “X” amount of time since the most recent occurrence of the event. This is called?debouncing.


For example, in a sign-up form, instead of sending a request once the user types to check for a duplicate username in the database, we can make the request get sent only 500 ms since the user last typed in the username input field (or of course, we could perform this duplicate check after the user submits the form instead of near real-time).

Since the?useDeferredValue?hook defers updates and causes additional re-render, it's important that you don't overuse it as this could actually cause the performance problems we're trying to avoid, as it forces React to do extra re-renders in your app. Use it only when you have critical updates that should happen as soon as possible without being slowed down by updates of lower priority.

Conclusion

The?useDeferred?value accepts a state variable and returns a copy of it that will not be updated until the component has been re-rendered from an update of the original state. This improves the performance and responsiveness of the app, as time-consuming updates are put off to a later time to make way for the critical ones that should be shown in the DOM without delay for the user to see.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了