DEV Community

Cover image for React Performance Trick: Updating Large Lists with Generators
IHesamI
IHesamI

Posted on

React Performance Trick: Updating Large Lists with Generators

Hi everyone. Recently, I had to implement a feature where items in a large list needed to update at very short intervals (as fast as 10 milliseconds).

At first, I took the straightforward route: keep an index in state, iterate over the list, and update items one by one. But this approach wasn’t ideal:

  1. Too many re-renders because the interval was so short.
  2. Extra overhead from constantly saving and retrieving the index state.

So, I tried a different approach: using a generator to control updates while each item manages its own visibility state.

Imagine we want to hide characters in a large string, one by one, with precise control over start, stop, and resume actions.

We’ll create a Char component that manages its own isVisible state and exposes its setter to the parent.

import { useEffect, useState, type Dispatch, type SetStateAction } from "react";

export default function Char({
  char,
  index,
  oninitial,
}: {
  char: string;
  index: number;
  oninitial: (x: Dispatch<SetStateAction<boolean>>) => void;
}) {
  const [isVisible, setIsVisible] = useState(true);

  useEffect(() => {
    oninitial(setIsVisible);
  }, []);

  return <span>{isVisible ? char : ""}</span>;
}
Enter fullscreen mode Exit fullscreen mode

The Parent Component

The parent (CharsList) stores all setters in a ref and uses a generator to walk through them at a controlled pace.

import { useRef, type Dispatch, type SetStateAction } from "react";
import Char from "./Char";

function CharsList({ largeString }: { largeString: string }) {
  const iterationRef = useRef<
    Generator<Dispatch<SetStateAction<boolean>>, void, unknown> | null
  >(null);

  const intervalRef = useRef<number | null>(null);

  const setters = useRef<Record<number, Dispatch<SetStateAction<boolean>>>>({});

  // Generator to yield each setter
  function* startChanger(arrayOfSetters: Dispatch<SetStateAction<boolean>>[]) {
    for (const setter of arrayOfSetters) yield setter;
  }

  const startHiding = () => {
    const iteration = startChanger(Object.values(setters.current));
    iterationRef.current = iteration;

    intervalRef.current = setInterval(() => {
      const result = iteration.next();
      if (result.done) {
        iterationRef.current = null;
        return clearInterval(intervalRef.current as number);
      }
      result.value(false);
    }, 10);
  };

  const stopHiding = () => clearInterval(intervalRef.current as number);

  const continueHiding = () => {
    const iteration = iterationRef.current!;
    intervalRef.current = setInterval(() => {
      const result = iteration.next();
      if (result.done) return clearInterval(intervalRef.current as number);
      result.value(false);
    }, 10);
  };

  return (
    <>
      <div className="card">
        <button onClick={startHiding}>Start Hiding</button>
        <button onClick={stopHiding}>Stop Hiding</button>
        <button onClick={continueHiding}>Continue Hiding</button>
      </div>

      <p className="large-string-text">
        {largeString.split("").map((char, index) => (
          <Char
            key={index}
            char={char}
            index={index}
            oninitial={(setter) => {
              setters.current[index] = setter;
            }}
          />
        ))}
      </p>
    </>
  );
}

export default CharsList;
Enter fullscreen mode Exit fullscreen mode

This approach gives you:

  1. Better performance — only one component updates at a time.
  2. Pause & resume control — thanks to the generator’s statefulness.

but Higher complexity — not as straightforward as the index-based solution.

If you’re working with large lists and ultra-short intervals, the performance tradeoff is worth it.

This was a fun problem to solve. At first, the generator approach felt unintuitive, but it became a powerful pattern for precise, performant updates.

Thanks for reading. Would love to hear your thoughts or alternative solutions in the comments!

Top comments (0)