'How to re-render a heavy React component with a low priority?

With some parameters, I have a React component that can take a few seconds to render but it is not a problem at all if it is not up-to-date.

To prevent the UI from being frozen, I would like to render it only when the user is not changing the form parameters.

It could be a defer, debounce, or another way of doing that. How would you achieve that? Thank you!



Solution 1:[1]

React 18

With React 18, a new API has been released that lets you mark state updates as less-important transition updates (as opposed to urgent updates)

Here's an example:

import {startTransition} from 'react';

// Urgent: Show what was typed
setInputValue(input);

// Mark any state updates inside as transitions
startTransition(() => {
  // Transition: Show the results
  setSearchQuery(input);
});

See https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-transitions

Another super helpful post with a real world example: https://github.com/reactwg/react-18/discussions/65

Solution 2:[2]

You could have an useEffect hook with no depedency array, so it updates every time the component is updated. On the hook, you'll start a timeout or interval to update the state of the component you want to lazy-render. At the return, you'll cancel the timer, which will be restarted at the next effect. That way, every time the form is updated, the rendering of the component is deferred.

Here is a codepen example that works as intended. There is a clock that always updates and the one in the button only updates when you're not interacting with it.

https://codepen.io/bernardofbbraga/pen/powdpqv

const element = <h1>Hello, world</h1>;

const Time = ({ time }) => new Date(time).toLocaleString();

const Clock = () => {
  const timer = React.useRef(null);
  const [time, setTime] = React.useState(Date.now());
  React.useEffect(() => {
    timer.current = setInterval(() => setTime(Date.now()), 1000);
    return () => clearInterval(timer.current);
  });

  return <Time time={time} />;
};

const Lazy = () => {
  const [counter, setCounter] = React.useState(1);
  return (
    <button onClick={() => setCounter(counter + 1)}>
      {" "}
      Counter:{counter}{" "}
      <div>
        This clock only updates when the user is not interacting with the button{" "}
        <Clock />{" "}
      </div>
    </button>
  );
};

ReactDOM.render(
  <div>
    App that updates frequently
    <div>
      <Clock />
    </div>
    <Lazy />
  </div>,
  document.getElementById("root")
);

Solution 3:[3]

You can use React.lazy and Suspense to load the component on demand.

Here I use a flag loadExtra to know when to load the extra component, offering a button to load it sooner than the 20 seconds the timeout is set for.

Edit new-dust-066uz

import { ..., lazy, Suspense } from "react";

const ExtraComponent = lazy(() => import("./ExtraComponent"));

const App = () => {
  const [loadExtra, setLoadExtra] = useState(false);

  ...
  <Suspense fallback="Loading...">
    <ExtraComponent />
  </Suspense>;
  ...
};

export default App;

I know of this trick, to force a Component re-render. By passing the key prop to the Component, when the key changes, the component will be forced to re-render

So whenever you deem the user is not changing the form, you can change the key and the React Component will be re-rendered.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 merlindru
Solution 2 Bernardo Ferreira Bastos Braga
Solution 3