import { FeaturePageHeader } from "../../src/components/docs-page/feature-page-header";
import {DemoBox} from "../../src/components/demo/demo-box";

<FeaturePageHeader
    title="Prop Memoization"
    subtitle="Feature making all props created by HT consistent and useable for memoization"
    feature="prop-memoization"
/>

By default, React props generated by `tree.getContainerProps()` and `item.getProps()` will not
be memoized, and might change their reference during each render as they are generated fresh
each time.

If you rely on stable props, or need them to be memoized, you can simply include the `propMemoizationFeature`
and the props will be memoized automatically. The feature doesn't require any configuration or expose any
methods.

## Example

Consider this sample, where the render method of each item is intentionally slowed down, resulting
in a slow usage experience when expanding or collapsing 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>
  );
};
```

Here, the render method is memoized and the propMemoizationFeature is included. Tree items still
render slowly during the initial render, but items that are unchanged during rerender do not retrigger
the slow render function, resulting in a faster experience when expanding or collapsing items:

```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>
  );
};
```