Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TreeView] Add lazy loading for children of tree items #15308

Merged
merged 64 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
0918085
add getChildrenCount
noraleonte Oct 28, 2024
45779ba
wip
noraleonte Oct 31, 2024
a29e2d0
wip
noraleonte Nov 6, 2024
765c2a5
wip
noraleonte Nov 6, 2024
7a3fad1
wip
noraleonte Nov 6, 2024
80bcc27
docs wip
noraleonte Nov 6, 2024
3e1e022
wip docs
noraleonte Nov 6, 2024
ea5a955
wip
noraleonte Nov 14, 2024
cf18e16
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Nov 15, 2024
ef26a40
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Nov 20, 2024
1aff38e
add selector
noraleonte Nov 20, 2024
5b9f5f8
wip
noraleonte Nov 20, 2024
addcdb8
wip
noraleonte Nov 20, 2024
c99a9dd
fix expansion and adjust initial state
noraleonte Nov 22, 2024
620578e
add loading and error for tree view
noraleonte Nov 22, 2024
26ebc5a
update error demo
noraleonte Nov 22, 2024
0d54f50
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Dec 3, 2024
aeefe05
rerun selection if needed
noraleonte Dec 3, 2024
081d8a3
check usability of dnd
noraleonte Dec 3, 2024
c31a456
rename treeViewDataSource -> dataSource
noraleonte Dec 3, 2024
ce5f0c5
fix initial state and error demos
noraleonte Dec 4, 2024
70a2fb8
add react-query demos
noraleonte Dec 5, 2024
04466c3
fix label editing
noraleonte Dec 9, 2024
6408dcb
wip
noraleonte Dec 9, 2024
0f5c1bd
fix label editing
noraleonte Dec 9, 2024
88dc0e8
move to pro
noraleonte Dec 9, 2024
321f004
update meta
noraleonte Dec 9, 2024
c552110
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Dec 9, 2024
fd58f94
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Dec 10, 2024
d449f19
docs:api
noraleonte Dec 10, 2024
6cc9de7
fix ts
noraleonte Dec 10, 2024
996a1b8
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Dec 10, 2024
a8a603c
add JSDocs
noraleonte Dec 10, 2024
90ff8b1
fix
noraleonte Dec 11, 2024
c5802a1
add tests
noraleonte Dec 11, 2024
9cbbd44
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Dec 11, 2024
a9bcc3d
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Dec 16, 2024
25ce29d
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Dec 23, 2024
70581b0
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Jan 7, 2025
8c96d04
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Jan 20, 2025
476dc72
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Jan 27, 2025
ec1c2e7
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Feb 6, 2025
651127a
update query library
noraleonte Feb 6, 2025
999a3b7
remove random line
noraleonte Feb 6, 2025
8f94e35
update editing md
noraleonte Feb 6, 2025
e91559b
fix
noraleonte Feb 6, 2025
d911bb0
fix
noraleonte Feb 6, 2025
38499c3
updates batch 1
noraleonte Feb 10, 2025
dad833b
batch 2
noraleonte Feb 10, 2025
7cb2532
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Feb 10, 2025
4f78bc9
Fix TS
flaviendelangle Feb 11, 2025
d1b573a
add slot for item error and loading state
noraleonte Feb 11, 2025
11f66ac
Merge branch 'lazy-loading-poc' of github.com:noraleonte/mui-x into l…
noraleonte Feb 11, 2025
e734f78
scripts
noraleonte Feb 11, 2025
f9fcea6
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Feb 11, 2025
016d256
fix
noraleonte Feb 11, 2025
b9df1b3
rename updateItemsState
noraleonte Feb 11, 2025
2973a25
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Feb 12, 2025
4796b78
fix typing issue
noraleonte Feb 12, 2025
1807791
fix
noraleonte Feb 12, 2025
2ea1dd3
fix ts errors
noraleonte Feb 19, 2025
d7f8c49
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Feb 19, 2025
4cf46d5
Merge branch 'master' of github.com:noraleonte/mui-x into lazy-loadin…
noraleonte Feb 26, 2025
bb5d8f5
update docs
noraleonte Feb 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/data/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,11 @@ const pages: MuiPage[] = [
{ pathname: '/x/react-tree-view/rich-tree-view/customization' },
{ pathname: '/x/react-tree-view/rich-tree-view/focus' },
{ pathname: '/x/react-tree-view/rich-tree-view/editing', newFeature: true },
{
pathname: '/x/react-tree-view/rich-tree-view/lazy-loading',
plan: 'pro',
newFeature: true,
},
{ pathname: '/x/react-tree-view/rich-tree-view/ordering', plan: 'pro', newFeature: true },
],
},
Expand Down
25 changes: 25 additions & 0 deletions docs/data/tree-view/rich-tree-view/editing/editing.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,28 @@
```

{{"demo": "ApiMethodUpdateItemLabel.js"}}

## Editing lazy loaded children

To store the updated item labels on your server use the `onItemLabelChange` callback function.

Changes to the label are not automatically updated in the `dataSourceCache` and will need to be updated manually.

Check warning on line 105 in docs/data/tree-view/rich-tree-view/editing/editing.md

View workflow job for this annotation

GitHub Actions / runner / vale

[vale] reported by reviewdog 🐶 [Google.Will] Avoid using 'will'. Raw Output: {"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "docs/data/tree-view/rich-tree-view/editing/editing.md", "range": {"start": {"line": 105, "column": 81}}}, "severity": "WARNING"}

```tsx
const handleItemLabelChange = (itemId: TreeViewItemId, newLabel: string) => {
// update your cache here
};

<RichTreeViewPro
items={[]}
onItemLabelChange={handleItemLabelChange}
isItemEditable
dataSource={{
getChildrenCount: (item) => item?.childrenCount as number,
getTreeItems: fetchData,
}}
{...otherProps}
/>;
```

Visit the dedicated page for [lazy loading](/x/react-tree-view/rich-tree-view/lazy-loading/#lazy-loading-and-label-editing) to read more.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Slider from '@mui/material/Slider';
import {
randomInt,
randomName,
randomId,
randomBoolean,
} from '@mui/x-data-grid-generator';
import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro';

function getSliderAriaValueText(value) {
return `${value}°C`;
}

export default function BasicLazyLoading() {
const [latency, setLatency] = React.useState(1000);

const handleSliderChange = (_event, newLatency) => {
setLatency(newLatency);
};

const fetchData = async () => {
const length = randomInt(5, 10);
const rows = Array.from({ length }, () => ({
id: randomId(),
label: randomName({}, {}),
...(randomBoolean() ? { childrenCount: length } : {}),
}));

return new Promise((resolve) => {
setTimeout(() => {
resolve(rows);
}, latency);
});
};

return (
<Box sx={{ width: '300px' }}>
<Box sx={{ width: 250 }}>
<Typography id="latency-slider" gutterBottom>
Loading latency: {latency} (ms)
</Typography>
<Slider
value={latency}
onChange={handleSliderChange}
aria-labelledby="latency-slider"
min={500}
max={10000}
shiftStep={1000}
step={500}
marks
getAriaValueText={getSliderAriaValueText}
valueLabelDisplay="auto"
/>
</Box>
<RichTreeViewPro
items={[]}
experimentalFeatures={{ lazyLoading: true }}
dataSource={{
getChildrenCount: (item) => item?.childrenCount,
getTreeItems: fetchData,
}}
/>
</Box>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Slider from '@mui/material/Slider';
import {
randomInt,
randomName,
randomId,
randomBoolean,
} from '@mui/x-data-grid-generator';
import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro';
import { TreeViewBaseItem } from '@mui/x-tree-view/models';

type ItemType = TreeViewBaseItem<{
id: string;
label: string;
childrenCount?: number;
}>;

function getSliderAriaValueText(value: number) {
return `${value}°C`;
}

export default function BasicLazyLoading() {
const [latency, setLatency] = React.useState(1000);

const handleSliderChange = (_event: Event, newLatency: number | number[]) => {
setLatency(newLatency as number);
};

const fetchData = async (): Promise<ItemType[]> => {
const length: number = randomInt(5, 10);
const rows = Array.from({ length }, () => ({
id: randomId(),
label: randomName({}, {}),
...(randomBoolean() ? { childrenCount: length } : {}),
}));

return new Promise((resolve) => {
setTimeout(() => {
resolve(rows);
}, latency);
});
};

return (
<Box sx={{ width: '300px' }}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<Box sx={{ width: '300px' }}>
<Box sx={{ minHeight: 320, minWidth: 300 }}>

<Box sx={{ width: 250 }}>
<Typography id="latency-slider" gutterBottom>
Loading latency: {latency} (ms)
</Typography>
<Slider
value={latency}
onChange={handleSliderChange}
aria-labelledby="latency-slider"
min={500}
max={10000}
shiftStep={1000}
step={500}
marks
getAriaValueText={getSliderAriaValueText}
valueLabelDisplay="auto"
/>
</Box>
<RichTreeViewPro
items={[]}
experimentalFeatures={{ lazyLoading: true }}
dataSource={{
getChildrenCount: (item) => item?.childrenCount as number,
getTreeItems: fetchData,
}}
/>
</Box>
);
}
48 changes: 48 additions & 0 deletions docs/data/tree-view/rich-tree-view/lazy-loading/ErrorManagement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from 'react';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import { randomName, randomId } from '@mui/x-data-grid-generator';
import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro';
import { initialItems } from './items';

export default function ErrorManagement() {
const [failRequests, setFailRequests] = React.useState(false);
const fetchData = async () => {
const rows = Array.from({ length: 10 }, () => ({
id: randomId(),
label: randomName({}, {}),
childrenCount: 10,
}));

// make the promise fail conditionally
return new Promise((resolve, reject) => {
setTimeout(() => {
if (failRequests) {
reject(new Error('Error fetching data'));
} else {
resolve(rows);
}
}, 1000);
});
};

return (
<Stack spacing={2} sx={{ width: '300px' }}>
<Button
onClick={() => setFailRequests((prev) => !prev)}
variant="outlined"
fullWidth
>
{failRequests ? 'Resolve requests' : 'Fail Requests'}
</Button>
<RichTreeViewPro
items={initialItems}
experimentalFeatures={{ lazyLoading: true }}
dataSource={{
getChildrenCount: (item) => item?.childrenCount,
getTreeItems: fetchData,
}}
/>
</Stack>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from 'react';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import { randomName, randomId } from '@mui/x-data-grid-generator';
import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro';
import { initialItems, ItemType } from './items';

export default function ErrorManagement() {
const [failRequests, setFailRequests] = React.useState(false);
const fetchData = async (): Promise<ItemType[]> => {
const rows = Array.from({ length: 10 }, () => ({
id: randomId(),
label: randomName({}, {}),
childrenCount: 10,
}));

// make the promise fail conditionally
return new Promise((resolve, reject) => {
setTimeout(() => {
if (failRequests) {
reject(new Error('Error fetching data'));
} else {
resolve(rows);
}
}, 1000);
});
};

return (
<Stack spacing={2} sx={{ width: '300px' }}>
<Button
onClick={() => setFailRequests((prev) => !prev)}
variant="outlined"
fullWidth
>
{failRequests ? 'Resolve requests' : 'Fail Requests'}
</Button>
<RichTreeViewPro
items={initialItems}
experimentalFeatures={{ lazyLoading: true }}
dataSource={{
getChildrenCount: (item) => item?.childrenCount as number,
getTreeItems: fetchData,
}}
/>
</Stack>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Button
onClick={() => setFailRequests((prev) => !prev)}
variant="outlined"
fullWidth
>
{failRequests ? 'Resolve requests' : 'Fail Requests'}
</Button>
<RichTreeViewPro
items={initialItems}
experimentalFeatures={{ lazyLoading: true }}
dataSource={{
getChildrenCount: (item) => item?.childrenCount as number,
getTreeItems: fetchData,
}}
/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as React from 'react';
import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro';
import {
randomInt,
randomName,
randomId,
randomBoolean,
} from '@mui/x-data-grid-generator';

import {
QueryClient,
QueryClientProvider,
useQueryClient,
} from '@tanstack/react-query';

const queryClient = new QueryClient();

const items = [];

const fetchData = async (_parentId) => {
const length = randomInt(5, 10);
const rows = Array.from({ length }, () => ({
id: randomId(),
label: randomName({}, {}),
...(randomBoolean() ? { childrenCount: length } : {}),
}));

return new Promise((resolve) => {
setTimeout(() => {
resolve(rows);
}, 1000);
});
};

export function FetchChildren() {
const myQueryClient = useQueryClient();

const fetchTreeItems = async (parentId) => {
const queryKey = parentId ? ['treeItems', parentId] : ['treeItems', 'root'];
const data = await myQueryClient.fetchQuery({
queryKey,
queryFn: () => fetchData(parentId),
});
return data;
};

return (
<RichTreeViewPro
items={items}
experimentalFeatures={{ lazyLoading: true }}
dataSource={{
getChildrenCount: (item) => item?.childrenCount,
getTreeItems: fetchTreeItems,
}}
/>
);
}

export default function FetchingWithReactQuery() {
return (
<QueryClientProvider client={queryClient}>
<FetchChildren />
</QueryClientProvider>
);
}
Loading
Loading