From 91300a873f57777d0f90833e89cdd5cd030ccd93 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Mon, 27 Sep 2021 11:02:07 -0400 Subject: [PATCH 1/5] perf(TreeView): add memoization + test demo --- .../src/components/TreeView/TreeView.tsx | 3 + .../components/TreeView/TreeViewListItem.tsx | 35 +- .../TreeView/examples/LargeTreeView.md | 103 +++++ .../components/TreeView/examples/TreeView.md | 380 +++++++++--------- 4 files changed, 339 insertions(+), 182 deletions(-) create mode 100644 packages/react-core/src/components/TreeView/examples/LargeTreeView.md diff --git a/packages/react-core/src/components/TreeView/TreeView.tsx b/packages/react-core/src/components/TreeView/TreeView.tsx index 469e4a6621e..ae1515136cd 100644 --- a/packages/react-core/src/components/TreeView/TreeView.tsx +++ b/packages/react-core/src/components/TreeView/TreeView.tsx @@ -69,6 +69,7 @@ export interface TreeViewProps { className?: string; /** Toolbar to display above the tree view */ toolbar?: React.ReactNode; + useMemo?: boolean; } export const TreeView: React.FunctionComponent = ({ @@ -89,6 +90,7 @@ export const TreeView: React.FunctionComponent = ({ activeItems, compareItems = (item, itemToCheck) => item.id === itemToCheck.id, className, + useMemo, ...props }: TreeViewProps) => { const treeViewList = ( @@ -116,6 +118,7 @@ export const TreeView: React.FunctionComponent = ({ action={item.action} compareItems={compareItems} isCompact={variant === 'compact' || variant === 'compactNoBackground'} + useMemo={useMemo} {...(item.children && { children: ( boolean; + useMemo?: boolean; } -export const TreeViewListItem: React.FunctionComponent = ({ +const TreeViewListItemBase: React.FunctionComponent = ({ name, title, id, @@ -78,10 +79,11 @@ export const TreeViewListItem: React.FunctionComponent = icon, expandedIcon, action, - compareItems + compareItems, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + useMemo }: TreeViewListItemProps) => { const [internalIsExpanded, setIsExpanded] = useState(defaultExpanded); - useEffect(() => { if (isExpanded !== undefined && isExpanded !== null) { setIsExpanded(isExpanded); @@ -207,4 +209,31 @@ export const TreeViewListItem: React.FunctionComponent = ); }; + +export const TreeViewListItem = React.memo(TreeViewListItemBase, (prevProps, nextProps) => { + const prevIncludes = + prevProps.activeItems && + prevProps.activeItems.length > 0 && + prevProps.activeItems.some( + item => prevProps.compareItems && item && prevProps.compareItems(item, prevProps.itemData) + ); + const nextIncludes = + nextProps.activeItems && + nextProps.activeItems.length > 0 && + nextProps.activeItems.some( + item => nextProps.compareItems && item && nextProps.compareItems(item, nextProps.itemData) + ); + // console.log( + // `id: ${id}. prevActiveIncludes? ${prevIncludes}. nextActiveIncludes? ${nextIncludes}. Should update? ${!( + // prevIncludes || nextIncludes + // )}` + // ); + + if (!nextProps.useMemo) { + return false; + } + + return !(prevIncludes || nextIncludes); +}); + TreeViewListItem.displayName = 'TreeViewListItem'; diff --git a/packages/react-core/src/components/TreeView/examples/LargeTreeView.md b/packages/react-core/src/components/TreeView/examples/LargeTreeView.md new file mode 100644 index 00000000000..e2d1f0f8168 --- /dev/null +++ b/packages/react-core/src/components/TreeView/examples/LargeTreeView.md @@ -0,0 +1,103 @@ +--- +id: Tree view +section: demos +--- + +## Examples + +### Large TreeView performance test + +```js +import React from 'react'; +import { TreeView, Button } from '@patternfly/react-core'; + +class PerfTreeView extends React.Component { + constructor(props) { + super(props); + + this.state = { activeItems: {}, allExpanded: true, useMemo: true }; + + this.onSelect = (evt, treeViewItem) => { + let filtered = []; + this.lotsOptions.forEach(item => this.filterItems(item, treeViewItem.id, filtered)); + this.setState({ + activeItems: filtered + }); + }; + + this.onToggle = evt => { + const { allExpanded } = this.state; + this.setState({ + allExpanded: allExpanded !== undefined ? !allExpanded : true + }); + }; + + this.onMemo = evt => { + this.setState({ + useMemo: !this.state.useMemo + }); + }; + + this.filterItems = (item, input, list) => { + if (item.children) { + let childContained = false; + item.children.forEach(child => { + if (childContained) { + this.filterItems(child, input, list); + } else { + childContained = this.filterItems(child, input, list); + } + }); + if (childContained) { + list.push(item); + } + } + + if (item.id === input) { + list.push(item); + return true; + } else { + return false; + } + }; + + this.lotsOptions = []; + for (let i = 0; i < 2000; i++) { + const childNum = Math.floor(Math.random() * 5) + 1; + let childOptions = []; + for (let j = 0; j < childNum; j++) { + childOptions.push({ name: 'Child ' + j, id: `Option${i} - Child${j}` }); + } + this.lotsOptions.push({ name: 'Option ' + i, id: i, children: childOptions }); + } + } + + render() { + const { activeItems, allExpanded, useMemo } = this.state; + const tree = ( + + ); + + return ( + + + + {tree} + + ); + } +} +``` diff --git a/packages/react-core/src/components/TreeView/examples/TreeView.md b/packages/react-core/src/components/TreeView/examples/TreeView.md index 11bddae1417..2d360a9b050 100644 --- a/packages/react-core/src/components/TreeView/examples/TreeView.md +++ b/packages/react-core/src/components/TreeView/examples/TreeView.md @@ -212,16 +212,21 @@ class SearchTreeView extends React.Component { render() { const { activeItems, filteredItems, isFiltered } = this.state; - + const toolbar = ( - - - - + + + + - - ); + + ); return ( this.mapTree(item)); - return ( - - ); + return ; } } ``` @@ -700,7 +703,14 @@ class CustomBadgesTreeView extends React.Component { name: 'Sources', id: 'Sources', customBadgeContent: '1 source', - children: [{ name: 'Application 4', id: 'App4', customBadgeContent: '1 child', children: [{ name: 'Settings', id: 'App4Settings' }] }] + children: [ + { + name: 'Application 4', + id: 'App4', + customBadgeContent: '1 child', + children: [{ name: 'Settings', id: 'App4Settings' }] + } + ] }, { name: 'Really really really long folder name that overflows the container it is in', @@ -853,64 +863,64 @@ import { TreeView, TreeViewDataItem } from '@patternfly/react-core'; const GuidesTreeView: React.FunctionComponent = () => { const options: TreeViewDataItem[] = [ - { - name: 'Application launcher', - id: 'AppLaunch', - children: [ - { - name: 'Application 1', - id: 'App1', - children: [ - { name: 'Settings', id: 'App1Settings' }, - { name: 'Current', id: 'App1Current' } - ] - }, - { - name: 'Application 2', - id: 'App2', - children: [ - { name: 'Settings', id: 'App2Settings' }, - { - name: 'Loader', - id: 'App2Loader', - children: [ - { name: 'Loading App 1', id: 'LoadApp1' }, - { name: 'Loading App 2', id: 'LoadApp2' }, - { name: 'Loading App 3', id: 'LoadApp3' } - ] - } - ] - } - ], - defaultExpanded: true - }, - { - name: 'Cost management', - id: 'Cost', - children: [ - { - name: 'Application 3', - id: 'App3', - children: [ - { name: 'Settings', id: 'App3Settings' }, - { name: 'Current', id: 'App3Current' } - ] - } - ] - }, - { - name: 'Sources', - id: 'Sources', - children: [{ name: 'Application 4', id: 'App4', children: [{ name: 'Settings', id: 'App4Settings' }] }] - }, - { - name: 'Really really really long folder name that overflows the container it is in', - id: 'Long', - children: [{ name: 'Application 5', id: 'App5' }] - } - ]; - return ; -} + { + name: 'Application launcher', + id: 'AppLaunch', + children: [ + { + name: 'Application 1', + id: 'App1', + children: [ + { name: 'Settings', id: 'App1Settings' }, + { name: 'Current', id: 'App1Current' } + ] + }, + { + name: 'Application 2', + id: 'App2', + children: [ + { name: 'Settings', id: 'App2Settings' }, + { + name: 'Loader', + id: 'App2Loader', + children: [ + { name: 'Loading App 1', id: 'LoadApp1' }, + { name: 'Loading App 2', id: 'LoadApp2' }, + { name: 'Loading App 3', id: 'LoadApp3' } + ] + } + ] + } + ], + defaultExpanded: true + }, + { + name: 'Cost management', + id: 'Cost', + children: [ + { + name: 'Application 3', + id: 'App3', + children: [ + { name: 'Settings', id: 'App3Settings' }, + { name: 'Current', id: 'App3Current' } + ] + } + ] + }, + { + name: 'Sources', + id: 'Sources', + children: [{ name: 'Application 4', id: 'App4', children: [{ name: 'Settings', id: 'App4Settings' }] }] + }, + { + name: 'Really really really long folder name that overflows the container it is in', + id: 'Long', + children: [{ name: 'Application 5', id: 'App5' }] + } + ]; + return ; +}; ``` ### Compact @@ -921,61 +931,67 @@ import { TreeView, TreeViewDataItem } from '@patternfly/react-core'; const CompactTreeView: React.FunctionComponent = () => { const options: TreeViewDataItem[] = [ - { - name: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value and may reject unrecognized values.', - title: 'apiVersion', - id: 'apiVersion' - }, - { - name: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated is CamelCase. More info:', - title: 'kind', - id: 'kind' - }, - { - name: 'Standard metadata object', - title: 'metadata', - id: 'metadata' - }, - { - name: 'Standard metadata object', - title: 'spec', - id: 'spec', - children: [ - { - name: 'Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Default to 0 (pod will be considered available as soon as it is ready).', - title: 'minReadySeconds', - id: 'minReadySeconds' - }, - { - name: 'Indicates that the deployment is paused', - title: 'paused', - id: 'paused' - }, - { - name: 'The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that the progress will not de estimated during the time a deployment is paused. Defaults to 600s.', - title: 'progressDeadlineSeconds', - id: 'progressDeadlineSeconds', - children: [ - { - name: 'The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.', - title: 'revisionHistoryLimit', - id: 'revisionHistoryLimit', - children: [ - { - name: 'Map of {key.value} pairs. A single {key.value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In" and the values array contains only "value". The requirements are ANDed.', - title: 'matchLabels', - id: 'matchLabels' - } - ] - } - ] - } - ] - } - ]; - return ; -} -``` + { + name: + 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value and may reject unrecognized values.', + title: 'apiVersion', + id: 'apiVersion' + }, + { + name: + 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated is CamelCase. More info:', + title: 'kind', + id: 'kind' + }, + { + name: 'Standard metadata object', + title: 'metadata', + id: 'metadata' + }, + { + name: 'Standard metadata object', + title: 'spec', + id: 'spec', + children: [ + { + name: + 'Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Default to 0 (pod will be considered available as soon as it is ready).', + title: 'minReadySeconds', + id: 'minReadySeconds' + }, + { + name: 'Indicates that the deployment is paused', + title: 'paused', + id: 'paused' + }, + { + name: + 'The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that the progress will not de estimated during the time a deployment is paused. Defaults to 600s.', + title: 'progressDeadlineSeconds', + id: 'progressDeadlineSeconds', + children: [ + { + name: + 'The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.', + title: 'revisionHistoryLimit', + id: 'revisionHistoryLimit', + children: [ + { + name: + 'Map of {key.value} pairs. A single {key.value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In" and the values array contains only "value". The requirements are ANDed.', + title: 'matchLabels', + id: 'matchLabels' + } + ] + } + ] + } + ] + } + ]; + return ; +}; +``` ### Compact, no background @@ -985,58 +1001,64 @@ import { TreeView, TreeViewDataItem } from '@patternfly/react-core'; const CompactNoBackgroundTreeView: React.FunctionComponent = () => { const options: TreeViewDataItem[] = [ - { - name: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value and may reject unrecognized values.', - title: 'apiVersion', - id: 'apiVersion' - }, - { - name: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated is CamelCase. More info:', - title: 'kind', - id: 'kind' - }, - { - name: 'Standard metadata object', - title: 'metadata', - id: 'metadata' - }, - { - name: 'Standard metadata object', - title: 'spec', - id: 'spec', - children: [ - { - name: 'Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Default to 0 (pod will be considered available as soon as it is ready).', - title: 'minReadySeconds', - id: 'minReadySeconds' - }, - { - name: 'Indicates that the deployment is paused', - title: 'paused', - id: 'paused' - }, - { - name: 'The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that the progress will not de estimated during the time a deployment is paused. Defaults to 600s.', - title: 'progressDeadlineSeconds', - id: 'progressDeadlineSeconds', - children: [ - { - name: 'The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.', - title: 'revisionHistoryLimit', - id: 'revisionHistoryLimit', - children: [ - { - name: 'Map of {key.value} pairs. A single {key.value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In" and the values array contains only "value". The requirements are ANDed.', - title: 'matchLabels', - id: 'matchLabels' - } - ] - } - ] - } - ] - } - ]; - return ; -} -``` \ No newline at end of file + { + name: + 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value and may reject unrecognized values.', + title: 'apiVersion', + id: 'apiVersion' + }, + { + name: + 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated is CamelCase. More info:', + title: 'kind', + id: 'kind' + }, + { + name: 'Standard metadata object', + title: 'metadata', + id: 'metadata' + }, + { + name: 'Standard metadata object', + title: 'spec', + id: 'spec', + children: [ + { + name: + 'Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Default to 0 (pod will be considered available as soon as it is ready).', + title: 'minReadySeconds', + id: 'minReadySeconds' + }, + { + name: 'Indicates that the deployment is paused', + title: 'paused', + id: 'paused' + }, + { + name: + 'The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that the progress will not de estimated during the time a deployment is paused. Defaults to 600s.', + title: 'progressDeadlineSeconds', + id: 'progressDeadlineSeconds', + children: [ + { + name: + 'The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.', + title: 'revisionHistoryLimit', + id: 'revisionHistoryLimit', + children: [ + { + name: + 'Map of {key.value} pairs. A single {key.value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In" and the values array contains only "value". The requirements are ANDed.', + title: 'matchLabels', + id: 'matchLabels' + } + ] + } + ] + } + ] + } + ]; + return ; +}; +``` From 071f430d8f54e041047335fdf6bac6c0cabcbd34 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 6 Oct 2021 11:40:41 -0400 Subject: [PATCH 2/5] remove demo, add description for memo --- .../src/components/TreeView/TreeView.tsx | 1 + .../components/TreeView/TreeViewListItem.tsx | 6 - .../TreeView/examples/LargeTreeView.md | 103 ------------------ 3 files changed, 1 insertion(+), 109 deletions(-) delete mode 100644 packages/react-core/src/components/TreeView/examples/LargeTreeView.md diff --git a/packages/react-core/src/components/TreeView/TreeView.tsx b/packages/react-core/src/components/TreeView/TreeView.tsx index ae1515136cd..147e6bf086d 100644 --- a/packages/react-core/src/components/TreeView/TreeView.tsx +++ b/packages/react-core/src/components/TreeView/TreeView.tsx @@ -69,6 +69,7 @@ export interface TreeViewProps { className?: string; /** Toolbar to display above the tree view */ toolbar?: React.ReactNode; + /** Flag indicating the TreeView should utilize memoization to help render large data sets. Setting this property requires that `activeItems` now pass in every node in the selected item's path. */ useMemo?: boolean; } diff --git a/packages/react-core/src/components/TreeView/TreeViewListItem.tsx b/packages/react-core/src/components/TreeView/TreeViewListItem.tsx index d4c5a0b0e41..7f728f7d61a 100644 --- a/packages/react-core/src/components/TreeView/TreeViewListItem.tsx +++ b/packages/react-core/src/components/TreeView/TreeViewListItem.tsx @@ -223,12 +223,6 @@ export const TreeViewListItem = React.memo(TreeViewListItemBase, (prevProps, nex nextProps.activeItems.some( item => nextProps.compareItems && item && nextProps.compareItems(item, nextProps.itemData) ); - // console.log( - // `id: ${id}. prevActiveIncludes? ${prevIncludes}. nextActiveIncludes? ${nextIncludes}. Should update? ${!( - // prevIncludes || nextIncludes - // )}` - // ); - if (!nextProps.useMemo) { return false; } diff --git a/packages/react-core/src/components/TreeView/examples/LargeTreeView.md b/packages/react-core/src/components/TreeView/examples/LargeTreeView.md deleted file mode 100644 index e2d1f0f8168..00000000000 --- a/packages/react-core/src/components/TreeView/examples/LargeTreeView.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -id: Tree view -section: demos ---- - -## Examples - -### Large TreeView performance test - -```js -import React from 'react'; -import { TreeView, Button } from '@patternfly/react-core'; - -class PerfTreeView extends React.Component { - constructor(props) { - super(props); - - this.state = { activeItems: {}, allExpanded: true, useMemo: true }; - - this.onSelect = (evt, treeViewItem) => { - let filtered = []; - this.lotsOptions.forEach(item => this.filterItems(item, treeViewItem.id, filtered)); - this.setState({ - activeItems: filtered - }); - }; - - this.onToggle = evt => { - const { allExpanded } = this.state; - this.setState({ - allExpanded: allExpanded !== undefined ? !allExpanded : true - }); - }; - - this.onMemo = evt => { - this.setState({ - useMemo: !this.state.useMemo - }); - }; - - this.filterItems = (item, input, list) => { - if (item.children) { - let childContained = false; - item.children.forEach(child => { - if (childContained) { - this.filterItems(child, input, list); - } else { - childContained = this.filterItems(child, input, list); - } - }); - if (childContained) { - list.push(item); - } - } - - if (item.id === input) { - list.push(item); - return true; - } else { - return false; - } - }; - - this.lotsOptions = []; - for (let i = 0; i < 2000; i++) { - const childNum = Math.floor(Math.random() * 5) + 1; - let childOptions = []; - for (let j = 0; j < childNum; j++) { - childOptions.push({ name: 'Child ' + j, id: `Option${i} - Child${j}` }); - } - this.lotsOptions.push({ name: 'Option ' + i, id: i, children: childOptions }); - } - } - - render() { - const { activeItems, allExpanded, useMemo } = this.state; - const tree = ( - - ); - - return ( - - - - {tree} - - ); - } -} -``` From d6e967da7dd85f10818e6c27b83ab59bacccf092 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 6 Oct 2021 11:41:38 -0400 Subject: [PATCH 3/5] update desc --- packages/react-core/src/components/TreeView/TreeView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-core/src/components/TreeView/TreeView.tsx b/packages/react-core/src/components/TreeView/TreeView.tsx index 147e6bf086d..74137f6c538 100644 --- a/packages/react-core/src/components/TreeView/TreeView.tsx +++ b/packages/react-core/src/components/TreeView/TreeView.tsx @@ -69,7 +69,7 @@ export interface TreeViewProps { className?: string; /** Toolbar to display above the tree view */ toolbar?: React.ReactNode; - /** Flag indicating the TreeView should utilize memoization to help render large data sets. Setting this property requires that `activeItems` now pass in every node in the selected item's path. */ + /** Flag indicating the TreeView should utilize memoization to help render large data sets. Setting this property requires that `activeItems` pass in an array containing every node in the selected item's path. */ useMemo?: boolean; } From 021ec7a74eb17f1383af81276724dfd522237ebc Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Wed, 6 Oct 2021 11:52:25 -0400 Subject: [PATCH 4/5] add smaller demo --- .../components/TreeView/examples/TreeView.md | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/packages/react-core/src/components/TreeView/examples/TreeView.md b/packages/react-core/src/components/TreeView/examples/TreeView.md index 2d360a9b050..518012b79ff 100644 --- a/packages/react-core/src/components/TreeView/examples/TreeView.md +++ b/packages/react-core/src/components/TreeView/examples/TreeView.md @@ -1062,3 +1062,91 @@ const CompactNoBackgroundTreeView: React.FunctionComponent = () => { return ; }; ``` + +### With memoization + +Turning on memoization with the `useMemo` property helps prevent unnecessary re-renders for large data sets. With this flag active, `activeItems` must pass in an array of nodes along the selected item's path to update properly. + +```js +import React from 'react'; +import { TreeView, Button } from '@patternfly/react-core'; + +class MemoTreeView extends React.Component { + constructor(props) { + super(props); + + this.state = { activeItems: {}, allExpanded: false }; + + this.onSelect = (evt, treeViewItem) => { + let filtered = []; + this.options.forEach(item => this.filterItems(item, treeViewItem.id, filtered)); + this.setState({ + activeItems: filtered + }); + }; + + this.onToggle = evt => { + const { allExpanded } = this.state; + this.setState({ + allExpanded: allExpanded !== undefined ? !allExpanded : true + }); + }; + + this.filterItems = (item, input, list) => { + if (item.children) { + let childContained = false; + item.children.forEach(child => { + if (childContained) { + this.filterItems(child, input, list); + } else { + childContained = this.filterItems(child, input, list); + } + }); + if (childContained) { + list.push(item); + } + } + + if (item.id === input) { + list.push(item); + return true; + } else { + return false; + } + }; + + this.options = []; + for (let i = 1; i <= 20; i++) { + const childNum = 5; + let childOptions = []; + for (let j = 1; j <= childNum; j++) { + childOptions.push({ name: 'Child ' + j, id: `Option${i} - Child${j}` }); + } + this.options.push({ name: 'Option ' + i, id: i, children: childOptions }); + } + } + + render() { + const { activeItems, allExpanded } = this.state; + const tree = ( + + ); + + return ( + + + {tree} + + ); + } +} +``` From 28243d6b124ef05154a01c4e2355307ac20f3d60 Mon Sep 17 00:00:00 2001 From: Katie McFaul Date: Tue, 12 Oct 2021 15:25:54 -0400 Subject: [PATCH 5/5] update memo function --- .../components/TreeView/TreeViewListItem.tsx | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/react-core/src/components/TreeView/TreeViewListItem.tsx b/packages/react-core/src/components/TreeView/TreeViewListItem.tsx index 7f728f7d61a..64bde18a206 100644 --- a/packages/react-core/src/components/TreeView/TreeViewListItem.tsx +++ b/packages/react-core/src/components/TreeView/TreeViewListItem.tsx @@ -53,6 +53,7 @@ export interface TreeViewListItemProps { action?: React.ReactNode; /** Callback for item comparison function */ compareItems?: (item: TreeViewDataItem, itemToCheck: TreeViewDataItem) => boolean; + /** Flag indicating the TreeView should utilize memoization to help render large data sets. Setting this property requires that `activeItems` pass in an array containing every node in the selected item's path. */ useMemo?: boolean; } @@ -211,6 +212,10 @@ const TreeViewListItemBase: React.FunctionComponent = ({ }; export const TreeViewListItem = React.memo(TreeViewListItemBase, (prevProps, nextProps) => { + if (!nextProps.useMemo) { + return false; + } + const prevIncludes = prevProps.activeItems && prevProps.activeItems.length > 0 && @@ -223,11 +228,35 @@ export const TreeViewListItem = React.memo(TreeViewListItemBase, (prevProps, nex nextProps.activeItems.some( item => nextProps.compareItems && item && nextProps.compareItems(item, nextProps.itemData) ); - if (!nextProps.useMemo) { + + if (prevIncludes || nextIncludes) { + return false; + } + + if ( + prevProps.name !== nextProps.name || + prevProps.title !== nextProps.title || + prevProps.id !== nextProps.id || + prevProps.isExpanded !== nextProps.isExpanded || + prevProps.defaultExpanded !== nextProps.defaultExpanded || + prevProps.onSelect !== nextProps.onSelect || + prevProps.onCheck !== nextProps.onCheck || + prevProps.hasCheck !== nextProps.hasCheck || + prevProps.checkProps !== nextProps.checkProps || + prevProps.hasBadge !== nextProps.hasBadge || + prevProps.customBadgeContent !== nextProps.customBadgeContent || + prevProps.badgeProps !== nextProps.badgeProps || + prevProps.isCompact !== nextProps.isCompact || + prevProps.icon !== nextProps.icon || + prevProps.expandedIcon !== nextProps.expandedIcon || + prevProps.action !== nextProps.action || + prevProps.parentItem !== nextProps.parentItem || + prevProps.itemData !== nextProps.itemData + ) { return false; } - return !(prevIncludes || nextIncludes); + return true; }); TreeViewListItem.displayName = 'TreeViewListItem';