Repository: spotify/backstage
File: plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx (TypeScript)

Code before:
function useProgressiveRender(rootNode: AppNode) {
  const [renderedNodes, setRenderedNodes] = useState<Set<string>>(new Set());
  const [isComplete, setIsComplete] = useState(false);
  const processingRef = useRef(false);
  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    if (processingRef.current) {
      return undefined;
    }

    processingRef.current = true;
    const allNodes = collectAllNodes(rootNode);

    const batchSize = 10;
    let currentIndex = 0;
    const rendered = new Set<string>();

    const processBatch = () => {
      const endIndex = Math.min(currentIndex + batchSize, allNodes.length);
      for (let i = currentIndex; i < endIndex; i++) {
        rendered.add(allNodes[i].spec.id);
      }
      currentIndex = endIndex;

      setRenderedNodes(new Set(rendered));

      if (currentIndex < allNodes.length) {
        timeoutRef.current = setTimeout(processBatch, 0);
      } else {
        setIsComplete(true);
        processingRef.current = false;
        timeoutRef.current = null;
      }
    };

    rendered.add(rootNode.spec.id);
    setRenderedNodes(new Set(rendered));
    currentIndex = 1;

    if (allNodes.length > 1) {
      timeoutRef.current = setTimeout(processBatch, 0);
    } else {
      setIsComplete(true);
      processingRef.current = false;
    }

    return () => {
      processingRef.current = false;
      if (timeoutRef.current !== null) {
        clearTimeout(timeoutRef.current);

Review comment (refactor):
Can this perhaps be made even simpler? It wondered if this could be a super thin helper style component that accepts an anonymous element array and then has a single state variable.

```tsx
const ProgressiveCollection = (props: {
  items: Element[]
}) => {
  const [maxIndex, setMaxIndex] = useState(0);

  // an effect that ticks the index += 10 if it's less than the items array length

  return (
    <>
      {items.slice(0, maxIndex).map((item, index) => {
        <Fragment key={index}>
          {item}
        </Fragment>
      })}
    </>
  );
}
```

Unless you think that it's even a problem that the parent has to JSX-render all the children upfront - which I suppose should not be the case.

Code after:
  useEffect(() => {
    if (maxIndex >= items.length) {
      return undefined;
    }

    const processBatch = () => {
      const nextIndex = Math.min(maxIndex + batchSize, items.length);
      setMaxIndex(nextIndex);

      if (nextIndex < items.length) {
        timeoutRef.current = setTimeout(processBatch, 0);
      }
    };

    timeoutRef.current = setTimeout(processBatch, 0);

    // eslint-disable-next-line consistent-return
    return () => {
      if (timeoutRef.current !== null) {
        clearTimeout(timeoutRef.current);
        timeoutRef.current = null;
      }
    };
  }, [maxIndex, items.length, batchSize]);

  return (
    <>
      {items.slice(0, maxIndex).map((item, index) => (
        <Fragment key={index}>{item()}</Fragment>
      ))}
    </>
  );
}

const Output = memo(
  function Output(props: {
    dataRef: ExtensionDataRef<unknown>;
    node?: AppNode;
  }) {
    const { dataRef, node } = props;
    const { id } = dataRef;
    const instance = node?.instance;

    const routeResolutionApi = useApi(routeResolutionApiRef);

    const { backgroundColor, color } = getOutputColor(id);

    const chipStyle: React.CSSProperties = {
      height: 20,
      padding: '0 10px',
      borderRadius: '10px',