diff --git a/change/@fluentui-react-tree-d2b17f80-1a10-4456-a7dd-89b53f9a27f4.json b/change/@fluentui-react-tree-d2b17f80-1a10-4456-a7dd-89b53f9a27f4.json new file mode 100644 index 0000000000000..17dbe4f6863fe --- /dev/null +++ b/change/@fluentui-react-tree-d2b17f80-1a10-4456-a7dd-89b53f9a27f4.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "feat: adds lazy loading story", + "packageName": "@fluentui/react-tree", + "email": "bernardo.sunderhus@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-tree/etc/react-tree.api.md b/packages/react-components/react-tree/etc/react-tree.api.md index 6a473c6814b21..0dd460d3d5810 100644 --- a/packages/react-components/react-tree/etc/react-tree.api.md +++ b/packages/react-components/react-tree/etc/react-tree.api.md @@ -60,6 +60,7 @@ export type FlatTreeItemProps = TreeItemProps & { // @public (undocumented) export type FlatTreeProps = Required, 'openItems' | 'onOpenChange' | 'onNavigation_unstable'>> & { ref: React_2.Ref; + openItems: ImmutableSet; }; // @public (undocumented) diff --git a/packages/react-components/react-tree/src/hooks/useFlatTree.ts b/packages/react-components/react-tree/src/hooks/useFlatTree.ts index f373c683ed87a..f6888be33b84a 100644 --- a/packages/react-components/react-tree/src/hooks/useFlatTree.ts +++ b/packages/react-components/react-tree/src/hooks/useFlatTree.ts @@ -12,6 +12,7 @@ import type { TreeProps, } from '../Tree'; import type { TreeItemProps } from '../TreeItem'; +import { ImmutableSet } from '../utils/ImmutableSet'; export type FlatTreeItemProps = TreeItemProps & { value: Value; @@ -40,7 +41,10 @@ export type FlatTreeItem = FlatTreeItem export type FlatTreeProps = Required< Pick, 'openItems' | 'onOpenChange' | 'onNavigation_unstable'> -> & { ref: React.Ref }; +> & { + ref: React.Ref; + openItems: ImmutableSet; +}; /** * FlatTree API to manage all required mechanisms to convert a list of items into renderable TreeItems diff --git a/packages/react-components/react-tree/stories/D_flatTree/TreeLazyLoading.md b/packages/react-components/react-tree/stories/D_flatTree/TreeLazyLoading.md new file mode 100644 index 0000000000000..6f0a7922844b2 --- /dev/null +++ b/packages/react-components/react-tree/stories/D_flatTree/TreeLazyLoading.md @@ -0,0 +1 @@ +In this lazy-loading hierarchical `Tree` structure example, the `LazyTreeItem` component and `useFlatTree` hook are utilized to create an efficient and dynamic tree. The tree loads and displays data on-demand, improving initial rendering time and performance. The `LazyTreeItem` component handles loading states and tree structure updates upon clicking a tree item. The `useFlatTree` hook converts the flat array of items into a tree structure, and allows control over individual `TreeItem` components' open/closed states using the `openItems` and `onOpenChange` props. diff --git a/packages/react-components/react-tree/stories/D_flatTree/TreeLazyLoading.stories.tsx b/packages/react-components/react-tree/stories/D_flatTree/TreeLazyLoading.stories.tsx new file mode 100644 index 0000000000000..6dffcabe2b440 --- /dev/null +++ b/packages/react-components/react-tree/stories/D_flatTree/TreeLazyLoading.stories.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; +import { + FlatTreeItemProps, + Tree, + TreeItem, + TreeItemLayout, + TreeOpenChangeData, + TreeOpenChangeEvent, + useFlatTree_unstable, +} from '@fluentui/react-tree'; +import story from './TreeLazyLoading.md'; +import { Spinner } from '@fluentui/react-components'; + +interface Result { + results: { name: string }[]; +} + +type Entity = FlatTreeItemProps & { name: string }; + +export const LazyLoading = () => { + const peopleTree = useQuery([]); + const planetsTree = useQuery([]); + const starshipsTree = useQuery([]); + const trees = { + people: peopleTree, + planets: planetsTree, + starships: starshipsTree, + }; + + const tree = React.useMemo( + () => [ + { + name: 'People', + value: 'people', + leaf: false, + }, + ...peopleTree.value, + { + name: 'Planets', + value: 'planets', + leaf: false, + }, + ...planetsTree.value, + { + name: 'Starship', + value: 'starships', + leaf: false, + }, + ...starshipsTree.value, + ], + [peopleTree, planetsTree, starshipsTree], + ); + + const handleOpenChange = (_: TreeOpenChangeEvent, data: TreeOpenChangeData) => { + if (data.open) { + if ( + (data.value === 'people' || data.value === 'planets' || data.value === 'starships') && + !trees[data.value].isLoaded + ) { + trees[data.value].query(() => + fetch(`https://swapi.dev/api/${data.value}`) + .then(result => result.json()) + .then((json: Result) => + json.results.map(people => ({ + value: `${data.value}/${people.name}`, + parentValue: data.value, + name: people.name, + })), + ), + ); + } + } + }; + + const flatTree = useFlatTree_unstable(tree, { onOpenChange: handleOpenChange }); + const treeProps = flatTree.getTreeProps(); + return ( + + {Array.from(flatTree.items(), item => { + const { name, ...itemProps } = item.getTreeItemProps(); + const { isLoading = false } = trees[item.value as 'people' | 'planets' | 'starships'] ?? {}; + return ( + : undefined} key={item.value} {...itemProps}> + {name} + + ); + })} + + ); +}; + +LazyLoading.parameters = { + docs: { + description: { + story, + }, + }, +}; + +/** + * This function is just for the sake of the example, + * a library for fetching data (like react-query) might be a better option + */ +function useQuery(initialValue: Value) { + const [queryResult, setQueryResult] = React.useState({ value: initialValue, isLoading: false, isLoaded: false }); + const query = (fn: () => Promise | Value) => { + setQueryResult(curr => ({ ...curr, isLoading: true })); + Promise.resolve(fn()).then(nextValue => { + setQueryResult({ value: nextValue, isLoaded: true, isLoading: false }); + }); + }; + return { ...queryResult, query } as const; +} diff --git a/packages/react-components/react-tree/stories/D_flatTree/index.stories.tsx b/packages/react-components/react-tree/stories/D_flatTree/index.stories.tsx index a054b8503018f..eb95c8aa5cd06 100644 --- a/packages/react-components/react-tree/stories/D_flatTree/index.stories.tsx +++ b/packages/react-components/react-tree/stories/D_flatTree/index.stories.tsx @@ -5,6 +5,7 @@ export { UseFlatTree as Default } from './useFlatTree.stories'; export { FlattenTree as flattenTree } from './flattenTree.stories'; export { Virtualization } from './Virtualization.stories'; export { AddRemoveTreeItem } from './TreeItemAddRemove.stories'; +export { LazyLoading } from './TreeLazyLoading.stories'; export default { title: 'Preview Components/Tree/flatTree',