We are Coltor Apps ― a software development agency behind this OSS. Need a reliable tech partner?

Let’s talk

Guides

Virtualization

About this guide

Virtualization is a technique for rendering very long lists efficiently. This short guide demonstrates how to virtualize both root-level entities and the children of parent entities.

For demonstration purposes, we'll use TanStack Virtual.

Virtualizing root entities

If you expect a large number of entities rendered at the root level, it’s a good idea to virtualize the list.

import { useRef } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";

import {
  BuilderEntity,
  useBuilderStore,
  useBuilderStoreData,
} from "@coltorapps/builder-react";

import { BuilderTextFieldEntity } from "./components";
import { formBuilder } from "./form-builder";

const components = { textField: BuilderTextFieldEntity };

export default function FormBuilderPage() {
  const builderStore = useBuilderStore(formBuilder);

  const rootIds = useBuilderStoreData(builderStore, (data) => data.schema.root);

  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: rootIds.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 35,
  });

  return (
    <div ref={parentRef}>
      {virtualizer.getVirtualItems().map((virtualRow) =>
        rootIds[virtualRow.index] ? (
          <div
            key={virtualRow.key}
            data-index={virtualRow.index}
            ref={virtualizer.measureElement}
          >
            <BuilderEntity
              entityId={rootIds[virtualRow.index]}
              builderStore={builderStore}
              components={components}
            />
          </div>
        ) : null,
      )}
    </div>
  );
}

We retrieve the array of root entity IDs from the builder store’s schema by using useBuilderStoreData with a custom selector: (data) => data.schema.root.

Note how instead of using <BuilderEntities />, we're using <BuilderEntity /> and implementing the render loop manually.

Virtualizing child entities

If a section entity contains a very long list of children, those can be virtualized as well.

import { useRef } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";

import { type BuilderEntityComponentProps } from "@coltorapps/builder-react";

import { type sectionEntity } from "./entities";
import { type formBuilder } from "./form-builder";

export function BuilderSectionEntity(
  props: BuilderEntityComponentProps<typeof sectionEntity, typeof formBuilder>,
) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: props.entity.children,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 35,
  });

  return (
    <div ref={parentRef}>
      {virtualizer.getVirtualItems().map((virtualRow) =>
        props.entity.children[virtualRow.index] ? (
          <div
            key={virtualRow.key}
            data-index={virtualRow.index}
            ref={virtualizer.measureElement}
          >
            <props.RenderChild
              entityId={props.entity.children[virtualRow.index]}
              builderStore={builderStore}
              components={components}
            />
          </div>
        ) : null,
      )}
    </div>
  );
}

Note that instead of using <props.RenderChildren />, we're using <props.RenderChild /> and implementing the render loop manually.

Previous
Entity hierarchies

Canary Branch