Skip to content

Commit

Permalink
[TreeView] Do not re-render every Tree Item when the Rich Tree View r…
Browse files Browse the repository at this point in the history
…e-renders (introduce selectors) (#14210)
  • Loading branch information
flaviendelangle authored Nov 15, 2024
1 parent a204305 commit 17486e9
Show file tree
Hide file tree
Showing 110 changed files with 2,022 additions and 1,086 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,46 @@ All the new Tree Item-related components and utils (introduced in the previous m
+ } from '@mui/x-tree-view/TreeItemLabelInput';
```

## Stop using `publicAPI` methods in the render

The Tree Items are now memoized to improve the performances of the Tree View components.
If you call a `publicAPI` method in the render of an item, it might not re-render and you might not have the new value.

```ts
function CustomTreeItem(props) {
const { publicAPI } = useTreeItemUtils();

// Invalid
console.log(publicAPI.getItem(props.itemId));

// Valid
React.useEffect(() => {
console.log(publicAPI.getItem(props.itemId));
});

// Valid
function handleItemClick() {
console.log(publicAPI.getItem(props.itemId));
}
}
```

If you need to access the tree item model inside the render, you can use the new `useTreeItemModel` hook:

```diff
+import { useTreeItemModel } from '@mui/x-tree-view/hooks';

function CustomTreeItem(props) {
- const { publicAPI } = useTreeItemUtils();
- const item = publicAPI.getItem(props.itemId);
+ const item = useTreeItemModel(props.itemId);
}
```

:::success
If you were using `publicAPI` methods to access other information than the tree item model inside the render, please open an issue so that we can provide a way to do it.
:::

## Apply the indentation on the item content instead of it's parent's group

The indentation of nested Tree Items is now applied on the content of the element.
Expand Down
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()}>
<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
Loading

0 comments on commit 17486e9

Please sign in to comment.