Skip to main content

React Compiler

At the moment, Headless Tree is compatible with project setups running React compiler only with certain workarounds. Headless Tree makes some implementation choices to improve performance, which are unfortunately not compatible with some of the assumption that React compiler makes.

There are two ways to use Headless Tree with React compiler:

  • Use an alternative entrypoint for Headless Tree optimized for usage with React compiler
  • Disable automatic memoization in components that use Headless Tree

Use an alternative entrypoint

Instead of importing useTree from @headless-tree/react, you can import it from @headless-tree/react/react-compiler. The interface of the hook from @headless-tree/react/react-compiler works slightly different, as it doesn't return a reference to the tree instance directly, but instead returns a function that can be called to get the current tree instance.

import { useTree } from "@headless-tree/react/react-compiler"; // alternative entrypoint

const MyTreeComponent = () => {
const tree = useTree({
// ...tree config
});

// call tree() to get the current tree instance
return (
<div {...tree().getContainerProps()}>
{tree().getItems().map((item) => (
// render item
))}
</div>
);
};

Disable automatic memoization

You can simply opt out of using React Compiler in components that use Headless Tree by adding the "use no memo"; directive at the top of the component file:

const MyTreeComponent = () => {
"use no memo"; // opt out of React Compiler

const tree = useTree({
// ...tree config
});

return (
<div {...tree.getContainerProps()}>
{tree.getItems().map((item) => (
// render item
))}
</div>
);
};

This doesn't disable the benefits of React Compiler for the rest of your application, but only for the specific component where you use Headless Tree.

Nature of the problem

Headless Tree is motivated in implementation and architecture by Tanstack Table, and Headless Tree is limited by the same constraints that prevent Tanstack Table from being currently compatible with React Compiler. Details can be found in this issue in facebook/react and this issue in tanstack/table.

The underlying issue is that React Compiler will transform const items = tree.getItems(); into something like const items = useMemo(() => tree.getItems(), [tree]);. However, Headless Tree maintains a stable reference to tree, so invocations to the tree instance methods will return stale values. The expectation by React Compiler is that all methods are pure in regards to their reference and their parameters. However, in order to support use cases with many tree items and virtualization, Headless Tree benefits from not maintaining large data sets in React state, and instead keeps them in internal data structures that can be accessed via instance methods.

The alternative entrypoint @headless-tree/react/react-compiler works almost identically to the normal entrypoint, but hides the tree instance behind a function call, which forces React Compiler not to memoize its child methods return values. This should not have performance disadvantages compared to normal integrations of Headless Tree into non-Compiler projects, since calls tree methods like tree.getItems() have very low overhead anyway.