Efficient lazy loading in nested Tree components with useAsyncList #8668
-
I’m building a file explorer UI using the Tree component, with nested directories and files. Since the number of files can be large, I’m using useAsyncList to support infinite scrolling. This works fine when there are no nested directories. However, once the tree includes nested directories, useAsyncList appears to fetch data for all nested levels up front, even before the user expands those folders. Ideally, I’d like to load data only for the root first, and then lazily fetch each subdirectory’s contents only when the user expands it. 2025-08-04.7.40.23.movHowever, it seems there’s no way to detect when a TreeItem is expanded from within the TreeItem component itself, which makes lazy loading difficult. Is there a recommended way to fetch data only when a folder is expanded? Here’s a simplified version of the code I’m working with: "use client";
import { useAsyncList } from "@react-stately/data";
import { Collection, TreeLoadMoreItem } from "react-aria-components";
import { Tree, TreeItem, TreeItemContent } from "@/components/ui/Tree";
interface DirectoryItem {
id: string;
type: "directory";
name: string;
}
interface FileItem {
id: string;
type: "file";
name: string;
}
type Item = DirectoryItem | FileItem;
function TreeLoader(props) {
return (
<TreeLoadMoreItem {...props}>
{({ level }) => <div>loading</div>}
</TreeLoadMoreItem>
);
}
export default function Page() {
return (
<div>
<Tree
aria-label="async loading tree"
style={{ height: "400px", width: "300px" }}
selectionMode="multiple"
>
<DirectoryTree id="root" name="root" type="directory" />
</Tree>
</div>
);
}
interface DirectoryTreeProps {
id: string;
name: string;
type: "directory";
}
function DirectoryTree(props: DirectoryTreeProps) {
const itemList = useAsyncList<Item>({
async load({ signal, cursor }) {
const res = await fetch(
cursor || `http://localhost:3000/tree/api?dirId=${props.id}`,
{ signal }
);
const json = await res.json();
return {
items: json.items,
cursor: json.next,
};
},
});
return (
<TreeItem id={props.id} textValue={props.name}>
<TreeItemContent>{props.name}</TreeItemContent>
<Collection items={itemList.items}>
{(item: Item) =>
item.type === "directory" ? (
<DirectoryTree id={item.id} name={item.name} type={item.type} />
) : (
<TreeItem id={item.id} textValue={item.name}>
<TreeItemContent>{item.name}</TreeItemContent>
</TreeItem>
)
}
</Collection>
<TreeLoader
isLoading={itemList.isLoading}
onLoadMore={itemList.loadMore}
/>
</TreeItem>
);
} Any advice or workaround would be greatly appreciated! Thanks in advance 🙇 |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 4 replies
-
Hi, thanks for the discussion. You should be able to use the context to get the Tree state in order to know if an item is expanded or not. https://react-spectrum.adobe.com/react-aria/Tree.html#advanced-customization I see you're using the recently released "LoadMoreItem" My guess is that you need loader items in the child items, not just the one at the root. Can you make it match the example a bit more closely, breaking up the async list call so it can independently fetch directories? then put those calls into the items with their own loaders? |
Beta Was this translation helpful? Give feedback.
Hi, thanks for the discussion. You should be able to use the context to get the Tree state in order to know if an item is expanded or not. https://react-spectrum.adobe.com/react-aria/Tree.html#advanced-customization
I see you're using the recently released "LoadMoreItem"
See https://react-spectrum.adobe.com/releases/2025-07-22.html and more specifically https://react-spectrum.adobe.com/react-aria/Tree.html#asynchronous-loading
My guess is that you need loader items in the child items, not just the one at the root. Can you make it match the example a bit more closely, breaking up the async list call so it can independently fetch directories? then put those calls into the items with their o…