import { DemoBox } from "../../src/components/demo/demo-box";

Sometimes, rendering tree items can be expensive. This can cause slow usage of the Tree react component,
since all items are rendered as part of a single flat list, and mutations to the displayed tree structure,
such as expanding or collapsing items or changing the tree will trigger a re-render of all items.

```ts jsx
import type { Meta } from "@storybook/react";
import React, { HTMLProps, forwardRef } from "react";
import {
  hotkeysCoreFeature,
  selectionFeature,
  syncDataLoaderFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import { action } from "storybook/actions";
import cn from "classnames";

const meta = {
  title: "React/Guides/Render Performance/Slow Item Renderers",
  tags: ["feature/propmemoization"],
} satisfies Meta;

export default meta;

// story-start
const SlowItem = forwardRef<HTMLButtonElement, HTMLProps<HTMLButtonElement>>(
  (props, ref) => {
    const start = Date.now();
    while (Date.now() - start < 20); // force the component to take 20ms to render
    action("renderItem")();
    return <button {...(props as any)} ref={ref} />;
  },
);

export const SlowItemRenderers = () => {
  const tree = useTree<string>({
    rootItemId: "folder",
    initialState: {
      expandedItems: ["folder-1", "folder-2", "folder-3"],
    },
    getItemName: (item) => item.getItemData(),
    isItemFolder: (item) => !item.getItemData().endsWith("item"),
    indent: 20,
    dataLoader: {
      getItem: (itemId) => itemId,
      getChildren: (itemId) => [
        `${itemId}-1`,
        `${itemId}-2`,
        `${itemId}-3`,
        `${itemId}-1item`,
        `${itemId}-2item`,
      ],
    },
    features: [syncDataLoaderFeature, selectionFeature, hotkeysCoreFeature],
  });

  return (
    <div {...tree.getContainerProps()} className="tree">
      {tree.getItems().map((item) => (
        <SlowItem
          {...item.getProps()}
          key={item.getId()}
          style={{ paddingLeft: `${item.getItemMeta().level * 20}px` }}
        >
          <div
            className={cn("treeitem", {
              focused: item.isFocused(),
              expanded: item.isExpanded(),
              selected: item.isSelected(),
              folder: item.isFolder(),
            })}
          >
            {item.getItemName()}
          </div>
        </SlowItem>
      ))}
    </div>
  );
};
```

Memoizing the individual tree item render methods can help reduce the performance impact of rendering
expensive tree items. This can be done using the `React.memo` function, which will only re-render the
component if the props have changed.

`React.memo` will only rerender its contents if any of its props have changed. Headless Tree doesn't
memoize props generated with `tree.getContainerProps()` and `item.getProps()` by default, and will
create new props during each render. By including the [Prop Memoization Feature](https://headless-tree.lukasbach.com/llm/features/propmemoization.md)
however, these props will be memoized automatically, making memoization of render items feasible.

Note that, in the sample below, expanding or collapsing individual items is much more efficient than
in the sample above, since now only those items that actually change will be rerendered.

```ts jsx
import type { Meta } from "@storybook/react";
import React, { HTMLProps, forwardRef, memo } from "react";
import {
  hotkeysCoreFeature,
  propMemoizationFeature,
  selectionFeature,
  syncDataLoaderFeature,
} from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import { action } from "storybook/actions";
import cn from "classnames";

const meta = {
  title: "React/Guides/Render Performance/Memoized Slow Item Renderers",
  tags: ["feature/propmemoization"],
} satisfies Meta;

export default meta;

// story-start
const SlowItem = forwardRef<
  HTMLButtonElement,
  HTMLProps<HTMLButtonElement> & {
    level: number;
    innerClass: string;
    title: string;
  }
>(({ level, innerClass, title, ...props }, ref) => {
  const start = Date.now();
  while (Date.now() - start < 20); // force the component to take 20ms to render
  action("renderItem")();
  return (
    <button
      {...(props as any)}
      ref={ref}
      style={{ paddingLeft: `${level * 20}px` }}
    >
      <div className={innerClass}>{title}</div>
    </button>
  );
});

const MemoizedItem = memo(SlowItem);

export const MemoizedSlowItemRenderers = () => {
  const tree = useTree<string>({
    rootItemId: "folder",
    initialState: {
      expandedItems: ["folder-1", "folder-2", "folder-3"],
    },
    getItemName: (item) => item.getItemData(),
    isItemFolder: (item) => !item.getItemData().endsWith("item"),
    indent: 20,
    dataLoader: {
      getItem: (itemId) => itemId,
      getChildren: (itemId) => [
        `${itemId}-1`,
        `${itemId}-2`,
        `${itemId}-3`,
        `${itemId}-1item`,
        `${itemId}-2item`,
      ],
    },
    features: [
      syncDataLoaderFeature,
      selectionFeature,
      hotkeysCoreFeature,
      propMemoizationFeature,
    ],
  });

  return (
    <div {...tree.getContainerProps()} className="tree">
      {tree.getItems().map((item) => (
        <MemoizedItem
          {...item.getProps()}
          key={item.getId()}
          level={item.getItemMeta().level}
          innerClass={cn("treeitem", {
            focused: item.isFocused(),
            expanded: item.isExpanded(),
            selected: item.isSelected(),
            folder: item.isFolder(),
          })}
          title={item.getItemName()}
        />
      ))}
    </div>
  );
};
```

You can further improve performance on large trees by making use of Virtualization, which is explained in
the [Virtualization Guide](https://headless-tree.lukasbach.com/llm/recipe/virtualization.md).
