From 422a17da455e3ac15d08364d51e324cbacd89f53 Mon Sep 17 00:00:00 2001 From: Petr Duda Date: Mon, 12 Jun 2023 16:36:31 +0200 Subject: [PATCH 1/3] tree --- .../D_flatTree/TreeInfiniteScrolling.md | 5 + .../TreeInfiniteScrolling.stories.tsx | 128 ++++++++++++++++++ .../stories/D_flatTree/index.stories.tsx | 1 + 3 files changed, 134 insertions(+) create mode 100644 packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.md create mode 100644 packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx diff --git a/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.md b/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.md new file mode 100644 index 00000000000000..c2eeb7b91cc6c4 --- /dev/null +++ b/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.md @@ -0,0 +1,5 @@ +The `InfiniteScrolling` example of the `Tree` component provides a dynamic and efficient Tree structure that loads more data as the user scrolls down the list. It uses the `useFlatTree` hook to manage a flat array of tree items, converting them into a hierarchical tree structure as needed. + +When the `Tree` is first rendered, a set number of `TreeItem` components are displayed, ensuring fast load times and efficient handling of potentially large amounts of data. As the user scrolls down the list, the `onScroll` event triggers the loading of more `TreeItem` components. + +This approach not only enhances the scalability of your application, but also improves the user experience by loading data as and when it is needed. The user is not overwhelmed with all the data at once and does not have to wait for large amounts of data to load initially. diff --git a/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx b/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx new file mode 100644 index 00000000000000..5eaf3de06e09a2 --- /dev/null +++ b/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx @@ -0,0 +1,128 @@ +import * as React from 'react'; +import { Tree, TreeItem, TreeItemLayout, useFlatTree_unstable, FlatTreeItemProps } from '@fluentui/react-tree'; +import { makeStyles, shorthands } from '@fluentui/react-components'; +import story from './TreeInfiniteScrolling.md'; +import { Spinner } from '../../../react-spinner/src/Spinner'; + +interface Result { + results: { name: string }[]; +} + +type Item = FlatTreeItemProps & { name: string | React.ReactNode }; + +const MAX_PAGES = 5; + +const pinnedItems = [ + { value: 'pinned', name: 'Pinned', id: 'pinned' }, + { value: 'pinned-item-1', parentValue: 'pinned', name: 'Pinned item 1' }, + { value: 'pinned-item-2', parentValue: 'pinned', name: 'Pinned item 2' }, + { value: 'pinned-item-3', parentValue: 'pinned', name: 'Pinned item 3' }, +]; + +const useStyles = makeStyles({ + container: { + height: '400px', + paddingBottom: '10px', + ...shorthands.overflow('auto'), + }, +}); + +export const InfiniteScrolling = () => { + const [page, setPage] = React.useState(1); + const [isLoading, setIsLoading] = React.useState(false); + const peopleItems = useQuery([ + { value: 'people', name: 'People' }, + ...Array.from({ length: 40 }, (_, index) => ({ + value: `person-${index + 1}`, + parentValue: 'people', + name: `Person ${index + 1}`, + })), + ]); + + const items = React.useMemo( + () => [ + ...pinnedItems, + ...peopleItems.value, + ...(isLoading + ? [ + { + value: 'isLoading', + parentValue: 'people', + name: , + }, + ] + : []), + ], + [isLoading, peopleItems], + ); + + const styles = useStyles(); + + const flatTree = useFlatTree_unstable(items, { defaultOpenItems: ['pinned', 'people'] }); + const listRef = React.useRef(null); + + const fetchMoreItems = () => { + setIsLoading(true); + + fetch(`https://swapi.dev/api/people?page=${page}`) + .then(res => res.json()) + .then((json: Result) => { + const fetchedItems = json.results.map(person => ({ + value: `person-${person.name}`, + parentValue: 'people', + name: person.name, + })); + + setIsLoading(false); + setPage(page + 1); + peopleItems.query(() => [...peopleItems.value, ...fetchedItems]); + }); + }; + + const handleScroll = (event: React.UIEvent) => { + const { scrollTop, scrollHeight, clientHeight } = event.currentTarget; + const hasReachedEnd = scrollHeight - scrollTop === clientHeight; + + if (!isLoading && hasReachedEnd && page < MAX_PAGES) { + fetchMoreItems(); + } + }; + + return ( +
+ + {Array.from(flatTree.items(), flatTreeItem => { + const { name, ...treeItemProps } = flatTreeItem.getTreeItemProps(); + return ( + + {name} + + ); + })} + +
+ ); +}; + +/** + * 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; +} + +InfiniteScrolling.parameters = { + docs: { + description: { + story, + }, + }, +}; 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 eb95c8aa5cd060..c538b33ca0d81f 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 @@ -6,6 +6,7 @@ export { FlattenTree as flattenTree } from './flattenTree.stories'; export { Virtualization } from './Virtualization.stories'; export { AddRemoveTreeItem } from './TreeItemAddRemove.stories'; export { LazyLoading } from './TreeLazyLoading.stories'; +export { InfiniteScrolling } from './TreeInfiniteScrolling.stories'; export default { title: 'Preview Components/Tree/flatTree', From 0e72ddc92ec567f8982904c25b93ecd4bc7b4844 Mon Sep 17 00:00:00 2001 From: Petr Duda Date: Tue, 13 Jun 2023 12:21:06 +0200 Subject: [PATCH 2/3] aria label --- .../stories/D_flatTree/TreeInfiniteScrolling.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx b/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx index 5eaf3de06e09a2..d81e794f0727ea 100644 --- a/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx +++ b/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx @@ -46,9 +46,9 @@ export const InfiniteScrolling = () => { ...(isLoading ? [ { - value: 'isLoading', + value: 'loading-people', parentValue: 'people', - name: , + name: , }, ] : []), From 08e5a19549e4a11fb7dd83aea6a620f742ab6bb2 Mon Sep 17 00:00:00 2001 From: Petr Duda Date: Thu, 22 Jun 2023 00:41:12 +0200 Subject: [PATCH 3/3] fix comments --- .../TreeInfiniteScrolling.stories.tsx | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx b/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx index d81e794f0727ea..7b5e8877fa0b1f 100644 --- a/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx +++ b/packages/react-components/react-tree/stories/D_flatTree/TreeInfiniteScrolling.stories.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; import { Tree, TreeItem, TreeItemLayout, useFlatTree_unstable, FlatTreeItemProps } from '@fluentui/react-tree'; -import { makeStyles, shorthands } from '@fluentui/react-components'; +import { makeStyles, shorthands, Spinner } from '@fluentui/react-components'; import story from './TreeInfiniteScrolling.md'; -import { Spinner } from '../../../react-spinner/src/Spinner'; interface Result { results: { name: string }[]; @@ -59,7 +58,6 @@ export const InfiniteScrolling = () => { const styles = useStyles(); const flatTree = useFlatTree_unstable(items, { defaultOpenItems: ['pinned', 'people'] }); - const listRef = React.useRef(null); const fetchMoreItems = () => { setIsLoading(true); @@ -89,18 +87,16 @@ export const InfiniteScrolling = () => { }; return ( -
- - {Array.from(flatTree.items(), flatTreeItem => { - const { name, ...treeItemProps } = flatTreeItem.getTreeItemProps(); - return ( - - {name} - - ); - })} - -
+ + {Array.from(flatTree.items(), flatTreeItem => { + const { name, ...treeItemProps } = flatTreeItem.getTreeItemProps(); + return ( + + {name} + + ); + })} + ); };