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] Do not re-render every Tree Item when the Rich Tree View re-renders (introduce selectors) #14210

Merged
merged 58 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
f0c8ac1
[TreeView] Introduce selectors
flaviendelangle Oct 23, 2024
a47c95e
Work
flaviendelangle Oct 23, 2024
e8a385c
Work
flaviendelangle Oct 23, 2024
0518706
Fix
flaviendelangle Oct 23, 2024
9ea8ab2
Fix tests
flaviendelangle Oct 23, 2024
2abe440
Merge
flaviendelangle Oct 23, 2024
9e302d2
Work for object children
flaviendelangle Oct 23, 2024
2307a65
Work
flaviendelangle Oct 24, 2024
479274f
Work
flaviendelangle Oct 24, 2024
eb00e1e
Work
flaviendelangle Oct 24, 2024
e9bbb6d
Work
flaviendelangle Oct 24, 2024
4b6ae73
Work
flaviendelangle Oct 24, 2024
99f3f07
Work
flaviendelangle Oct 24, 2024
a70d3dd
Work
flaviendelangle Oct 24, 2024
87d1992
Fix doc examples
flaviendelangle Oct 24, 2024
3cdc8f2
Work
flaviendelangle Oct 24, 2024
1181d38
Fix
flaviendelangle Oct 24, 2024
79228ef
Fix
flaviendelangle Oct 24, 2024
4cef1db
Merge branch 'master' into selector-tree-view
flaviendelangle Oct 24, 2024
66fde8c
Fix
flaviendelangle Oct 24, 2024
4987b9a
Fix
flaviendelangle Oct 24, 2024
451bac2
Fix
flaviendelangle Oct 24, 2024
9302614
Merge branch 'master' into selector-tree-view
flaviendelangle Oct 25, 2024
3b63a9d
Remove forceUpdate
flaviendelangle Oct 25, 2024
01879e5
Fix
flaviendelangle Oct 25, 2024
f834dca
Work
flaviendelangle Oct 25, 2024
efae98d
Add tests
flaviendelangle Oct 25, 2024
ab57913
Work
flaviendelangle Oct 25, 2024
d9f5dc9
Work
flaviendelangle Oct 25, 2024
7492a5e
Add JSDoc and improve DX
flaviendelangle Oct 25, 2024
da44f3a
Work
flaviendelangle Oct 25, 2024
315c107
Regen api
flaviendelangle Oct 25, 2024
f3d1fda
Fix
flaviendelangle Oct 25, 2024
59518ea
Fix
flaviendelangle Oct 25, 2024
96e4287
Fix
flaviendelangle Oct 25, 2024
909ed67
Merge branch 'master' into selector-tree-view
flaviendelangle Oct 28, 2024
3412fea
Merge branch 'master' into selector-tree-view
flaviendelangle Oct 29, 2024
41967f2
Add tests
flaviendelangle Oct 29, 2024
ad27780
Fix
flaviendelangle Oct 29, 2024
bf00041
Fix
flaviendelangle Oct 29, 2024
bb27ecb
Merge branch 'master' into selector-tree-view
flaviendelangle Oct 29, 2024
86662f6
Fix
flaviendelangle Oct 29, 2024
608757e
Fix
flaviendelangle Oct 29, 2024
095c407
Merge
flaviendelangle Oct 31, 2024
c0871e3
Merge
flaviendelangle Nov 7, 2024
fb0c67d
Merge
flaviendelangle Nov 7, 2024
2e8eec2
Merge branch 'master' into selector-tree-view
flaviendelangle Nov 8, 2024
93a8d0f
Merge branch 'master' into selector-tree-view
flaviendelangle Nov 12, 2024
43d7d3f
Review: Nora
flaviendelangle Nov 12, 2024
21b820c
Merge branch 'master' into selector-tree-view
flaviendelangle Nov 14, 2024
aaa6c7d
Add migration guide
flaviendelangle Nov 14, 2024
5172348
Merge branch 'master' into selector-tree-view
flaviendelangle Nov 14, 2024
d10fcc9
Merge branch 'master' into selector-tree-view
flaviendelangle Nov 14, 2024
5933ee6
Merge branch 'master' into selector-tree-view
flaviendelangle Nov 14, 2024
7fdd27d
Empty
flaviendelangle Nov 14, 2024
4c37ec2
Merge branch 'master' into selector-tree-view
flaviendelangle Nov 14, 2024
4d125f6
Merge branch 'master' into selector-tree-view
flaviendelangle Nov 15, 2024
fa353bd
Review: Nora
flaviendelangle Nov 15, 2024
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
23 changes: 10 additions & 13 deletions docs/data/tree-view/rich-tree-view/customization/FileExplorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon';
import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider';
import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay';
import { useTreeItemModel } from '@mui/x-tree-view/hooks';

const ITEMS = [
{
Expand Down Expand Up @@ -178,13 +179,6 @@ function CustomLabel({ icon: Icon, expandable, children, ...other }) {
);
}

const isExpandable = (reactChildren) => {
if (Array.isArray(reactChildren)) {
return reactChildren.length > 0 && reactChildren.some(isExpandable);
}
return Boolean(reactChildren);
};

const getIconFromFileType = (fileType) => {
switch (fileType) {
case 'image':
Expand All @@ -210,6 +204,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) {
const { id, itemId, label, disabled, children, ...other } = props;

const {
getContextProviderProps,
getRootProps,
getContentProps,
getIconContainerProps,
Expand All @@ -218,20 +213,19 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) {
getGroupTransitionProps,
getDragAndDropOverlayProps,
status,
publicAPI,
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });

const item = publicAPI.getItem(itemId);
const expandable = isExpandable(children);
const item = useTreeItemModel(itemId);

let icon;
if (expandable) {
if (status.expandable) {
icon = FolderRounded;
} else if (item.fileType) {
icon = getIconFromFileType(item.fileType);
}

return (
<TreeItemProvider itemId={itemId}>
<TreeItemProvider {...getContextProviderProps()}>
Copy link
Member Author

Choose a reason for hiding this comment

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

This is a very dumb BC...
I need the id attribute in TreeViewChildrenItemProvider to be able to generate the defaultized id attribute on the 1st render?

Why?
Because state.items.itemMeta is empty on the 1st render, before this PR it was not a problem because the item was rendering twice (because of lack of memoization) so we would get the item meta on the 2nd render.
But now we only have one render and we can't access the itemMeta from the state.
So we need another way to access the id attribute manually passed to TreeItem in SimpleTreeView.

I should have created getContextProviderProps from the beginning "just in case"

<StyledTreeItemRoot {...getRootProps(other)}>
<CustomTreeItemContent
{...getContentProps({
Expand All @@ -248,7 +242,10 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) {
</TreeItemIconContainer>
<TreeItemCheckbox {...getCheckboxProps()} />
<CustomLabel
{...getLabelProps({ icon, expandable: expandable && status.expanded })}
{...getLabelProps({
icon,
expandable: status.expandable && status.expanded,
})}
/>
<TreeItemDragAndDropOverlay {...getDragAndDropOverlayProps()} />
</CustomTreeItemContent>
Expand Down
23 changes: 10 additions & 13 deletions docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon';
import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider';
import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay';
import { useTreeItemModel } from '@mui/x-tree-view/hooks';
import { TreeViewBaseItem } from '@mui/x-tree-view/models';

type FileType = 'image' | 'pdf' | 'doc' | 'video' | 'folder' | 'pinned' | 'trash';
Expand Down Expand Up @@ -204,13 +205,6 @@ function CustomLabel({
);
}

const isExpandable = (reactChildren: React.ReactNode) => {
if (Array.isArray(reactChildren)) {
return reactChildren.length > 0 && reactChildren.some(isExpandable);
}
return Boolean(reactChildren);
};

const getIconFromFileType = (fileType: FileType) => {
switch (fileType) {
case 'image':
Expand Down Expand Up @@ -243,6 +237,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(
const { id, itemId, label, disabled, children, ...other } = props;

const {
getContextProviderProps,
getRootProps,
getContentProps,
getIconContainerProps,
Expand All @@ -251,20 +246,19 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(
getGroupTransitionProps,
getDragAndDropOverlayProps,
status,
publicAPI,
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });

const item = publicAPI.getItem(itemId);
const expandable = isExpandable(children);
const item = useTreeItemModel<ExtendedTreeItemProps>(itemId)!;

let icon;
if (expandable) {
if (status.expandable) {
icon = FolderRounded;
} else if (item.fileType) {
icon = getIconFromFileType(item.fileType);
}

return (
<TreeItemProvider itemId={itemId}>
<TreeItemProvider {...getContextProviderProps()}>
<StyledTreeItemRoot {...getRootProps(other)}>
<CustomTreeItemContent
{...getContentProps({
Expand All @@ -281,7 +275,10 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(
</TreeItemIconContainer>
<TreeItemCheckbox {...getCheckboxProps()} />
<CustomLabel
{...getLabelProps({ icon, expandable: expandable && status.expanded })}
{...getLabelProps({
icon,
expandable: status.expandable && status.expanded,
})}
/>
<TreeItemDragAndDropOverlay {...getDragAndDropOverlayProps()} />
</CustomTreeItemContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) {
const { id, itemId, label, disabled, children, ...other } = props;

const {
getContextProviderProps,
getRootProps,
getContentProps,
getIconContainerProps,
Expand All @@ -51,7 +52,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) {
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });

return (
<TreeItemProvider itemId={itemId}>
<TreeItemProvider {...getContextProviderProps()}>
<TreeItemRoot {...getRootProps(other)}>
<TreeItemContent {...getContentProps()}>
<TreeItemIconContainer {...getIconContainerProps()}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(
const { id, itemId, label, disabled, children, ...other } = props;

const {
getContextProviderProps,
getRootProps,
getContentProps,
getIconContainerProps,
Expand All @@ -58,7 +59,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });

return (
<TreeItemProvider itemId={itemId}>
<TreeItemProvider {...getContextProviderProps()}>
<TreeItemRoot {...getRootProps(other)}>
<TreeItemContent {...getContentProps()}>
<TreeItemIconContainer {...getIconContainerProps()}>
Expand Down
11 changes: 6 additions & 5 deletions docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import IconButton from '@mui/material/IconButton';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import { TreeItem, TreeItemLabel } from '@mui/x-tree-view/TreeItem';
import { useTreeItem } from '@mui/x-tree-view/useTreeItem';
import { useTreeItemUtils } from '@mui/x-tree-view/hooks';

import { useTreeItemUtils, useTreeItemModel } from '@mui/x-tree-view/hooks';

const StyledLabelInput = styled('input')(({ theme }) => ({
...theme.typography.body1,
Expand Down Expand Up @@ -69,9 +69,11 @@ function Label({ children, ...other }) {
}

const LabelInput = React.forwardRef(function LabelInput(
{ item, handleCancelItemLabelEditing, handleSaveItemLabel, ...props },
{ itemId, handleCancelItemLabelEditing, handleSaveItemLabel, ...props },
ref,
) {
const item = useTreeItemModel(itemId);

const [initialNameValue, setInitialNameValue] = React.useState({
firstName: item.firstName,
lastName: item.lastName,
Expand Down Expand Up @@ -141,7 +143,6 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) {
itemId: props.itemId,
children: props.children,
});
const { publicAPI } = useTreeItem(props);

const handleInputBlur = (event) => {
event.defaultMuiPrevented = true;
Expand All @@ -158,7 +159,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) {
slots={{ label: Label, labelInput: LabelInput }}
slotProps={{
labelInput: {
item: publicAPI.getItem(props.itemId),
itemId: props.itemId,
onBlur: handleInputBlur,
onKeyDown: handleInputKeyDown,
handleCancelItemLabelEditing: interactions.handleCancelItemLabelEditing,
Expand Down
14 changes: 7 additions & 7 deletions docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import { TreeItem, TreeItemLabel, TreeItemProps } from '@mui/x-tree-view/TreeIte
import {
UseTreeItemLabelInputSlotOwnProps,
UseTreeItemLabelSlotOwnProps,
useTreeItem,
} from '@mui/x-tree-view/useTreeItem';
import { useTreeItemUtils } from '@mui/x-tree-view/hooks';
import { TreeViewBaseItem } from '@mui/x-tree-view/models';
import { useTreeItemUtils, useTreeItemModel } from '@mui/x-tree-view/hooks';
import { TreeViewBaseItem, TreeViewItemId } from '@mui/x-tree-view/models';

const StyledLabelInput = styled('input')(({ theme }) => ({
...theme.typography.body1,
Expand Down Expand Up @@ -83,18 +82,20 @@ function Label({ children, ...other }: UseTreeItemLabelSlotOwnProps) {
interface CustomLabelInputProps extends UseTreeItemLabelInputSlotOwnProps {
handleCancelItemLabelEditing: (event: React.SyntheticEvent) => void;
handleSaveItemLabel: (event: React.SyntheticEvent, label: string) => void;
item: TreeViewBaseItem<ExtendedTreeItemProps>;
itemId: TreeViewItemId;
}

const LabelInput = React.forwardRef(function LabelInput(
{
item,
itemId,
handleCancelItemLabelEditing,
handleSaveItemLabel,
...props
}: Omit<CustomLabelInputProps, 'ref'>,
ref: React.Ref<HTMLInputElement>,
) {
const item = useTreeItemModel<ExtendedTreeItemProps>(itemId)!;

const [initialNameValue, setInitialNameValue] = React.useState({
firstName: item.firstName,
lastName: item.lastName,
Expand Down Expand Up @@ -167,7 +168,6 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(
itemId: props.itemId,
children: props.children,
});
const { publicAPI } = useTreeItem(props);

const handleInputBlur: UseTreeItemLabelInputSlotOwnProps['onBlur'] = (event) => {
event.defaultMuiPrevented = true;
Expand All @@ -186,7 +186,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(
slots={{ label: Label, labelInput: LabelInput }}
slotProps={{
labelInput: {
item: publicAPI.getItem(props.itemId),
itemId: props.itemId,
onBlur: handleInputBlur,
onKeyDown: handleInputKeyDown,
handleCancelItemLabelEditing: interactions.handleCancelItemLabelEditing,
Expand Down
21 changes: 7 additions & 14 deletions docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import {
RichTreeViewRoot,
RICH_TREE_VIEW_PLUGINS,
} from '@mui/x-tree-view/RichTreeView';
import { TreeItem } from '@mui/x-tree-view/TreeItem';
import { useTreeView, TreeViewProvider } from '@mui/x-tree-view/internals';
import {
useTreeView,
TreeViewProvider,
RichTreeViewItems,
} from '@mui/x-tree-view/internals';

const useTreeViewLogExpanded = ({ params, models }) => {
const expandedStr = JSON.stringify(models.expandedItems.value);
Expand Down Expand Up @@ -36,25 +39,15 @@ useTreeViewLogExpanded.params = {
const TREE_VIEW_PLUGINS = [...RICH_TREE_VIEW_PLUGINS, useTreeViewLogExpanded];

function TreeView(props) {
const { getRootProps, contextValue, instance } = useTreeView({
const { getRootProps, contextValue } = useTreeView({
plugins: TREE_VIEW_PLUGINS,
props,
});

const itemsToRender = instance.getItemsToRender();

const renderItem = ({ children: itemChildren, ...itemProps }) => {
return (
<TreeItem key={itemProps.itemId} {...itemProps}>
{itemChildren?.map(renderItem)}
</TreeItem>
);
};

return (
<TreeViewProvider value={contextValue}>
<RichTreeViewRoot {...getRootProps()}>
{itemsToRender.map(renderItem)}
<RichTreeViewItems slots={undefined} slotProps={undefined} />
</RichTreeViewRoot>
</TreeViewProvider>
);
Expand Down
19 changes: 3 additions & 16 deletions docs/data/tree-view/rich-tree-view/headless/LogExpandedItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import {
RichTreeViewPluginSlots,
RichTreeViewPluginSlotProps,
} from '@mui/x-tree-view/RichTreeView';
import { TreeItem } from '@mui/x-tree-view/TreeItem';
import {
UseTreeViewExpansionSignature,
TreeViewPlugin,
TreeViewPluginSignature,
useTreeView,
TreeViewProvider,
ConvertPluginsIntoSignatures,
RichTreeViewItems,
} from '@mui/x-tree-view/internals';

interface TreeViewLogExpandedParameters {
Expand Down Expand Up @@ -86,31 +86,18 @@ type TreeViewPluginSignatures = ConvertPluginsIntoSignatures<
function TreeView<R extends {}, Multiple extends boolean | undefined>(
props: TreeViewProps<R, Multiple>,
) {
const { getRootProps, contextValue, instance } = useTreeView<
const { getRootProps, contextValue } = useTreeView<
TreeViewPluginSignatures,
typeof props
>({
plugins: TREE_VIEW_PLUGINS,
props,
});

const itemsToRender = instance.getItemsToRender();

const renderItem = ({
children: itemChildren,
...itemProps
}: ReturnType<typeof instance.getItemsToRender>[number]) => {
return (
<TreeItem key={itemProps.itemId} {...itemProps}>
{itemChildren?.map(renderItem)}
</TreeItem>
);
};

return (
<TreeViewProvider value={contextValue}>
<RichTreeViewRoot {...getRootProps()}>
{itemsToRender.map(renderItem)}
<RichTreeViewItems slots={undefined} slotProps={undefined} />
</RichTreeViewRoot>
</TreeViewProvider>
);
Expand Down
2 changes: 1 addition & 1 deletion docs/data/tree-view/rich-tree-view/headless/headless.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ const useCustomPlugin = ({ models }) => {
models.expandedItems.setValue([]);

// Check if an item is expanded
const isExpanded = instance.isNodeExpanded('some-item-id');
const isExpanded = useSelector(selectorIsItemExpanded, 'some-item-id');
};
};
```
Expand Down
Loading