Skip to content

Commit 1888f9f

Browse files
authored
Merge pull request #2638 from headlamp-k8s/sidebar-perf
frontend Sidebar: Improve performance by moving isSelected logic to the parent component
2 parents d826085 + ffc648b commit 1888f9f

File tree

3 files changed

+50
-48
lines changed

3 files changed

+50
-48
lines changed

frontend/src/components/Sidebar/Sidebar.tsx

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,8 @@ import { ActionButton } from '../common';
1717
import CreateButton from '../common/Resource/CreateButton';
1818
import NavigationTabs from './NavigationTabs';
1919
import prepareRoutes from './prepareRoutes';
20-
import SidebarItem from './SidebarItem';
21-
import {
22-
DefaultSidebars,
23-
setSidebarSelected,
24-
setWhetherSidebarOpen,
25-
SidebarEntry,
26-
} from './sidebarSlice';
20+
import SidebarItem, { SidebarItemProps } from './SidebarItem';
21+
import { DefaultSidebars, setSidebarSelected, setWhetherSidebarOpen } from './sidebarSlice';
2722
import VersionButton from './VersionButton';
2823

2924
export const drawerWidth = 240;
@@ -153,6 +148,34 @@ const DefaultLinkArea = memo((props: { sidebarName: string; isOpen: boolean }) =
153148
);
154149
});
155150

151+
/**
152+
* Checks if item or any sub items are selected
153+
*/
154+
function getIsSelected(item: SidebarItemProps, selectedName?: string | null): boolean {
155+
if (!selectedName) return false;
156+
return (
157+
item.name === selectedName || Boolean(item.subList?.find(it => getIsSelected(it, selectedName)))
158+
);
159+
}
160+
161+
/**
162+
* Updates the isSelected field of an item
163+
*/
164+
function updateItemSelected(
165+
item: SidebarItemProps,
166+
selectedName?: string | null
167+
): SidebarItemProps {
168+
const isSelected = getIsSelected(item, selectedName);
169+
if (isSelected === false) return item;
170+
return {
171+
...item,
172+
isSelected: isSelected,
173+
subList: item.subList
174+
? item.subList.map(it => updateItemSelected(it, selectedName))
175+
: item.subList,
176+
};
177+
}
178+
156179
export default function Sidebar() {
157180
const { t, i18n } = useTranslation(['glossary', 'translation']);
158181

@@ -177,7 +200,7 @@ export default function Sidebar() {
177200
return prepareRoutes(t, sidebar.selected.sidebar || '');
178201
}, [
179202
cluster,
180-
sidebar.selected,
203+
sidebar.selected.sidebar,
181204
sidebar.entries,
182205
sidebar.filters,
183206
i18n.language,
@@ -195,13 +218,18 @@ export default function Sidebar() {
195218
[sidebar.selected.sidebar, isOpen]
196219
);
197220

221+
const processedItems = useMemo(
222+
() => items.map(item => updateItemSelected(item, sidebar.selected.item)),
223+
[items, sidebar.selected.item]
224+
);
225+
198226
if (sidebar.selected.sidebar === null || !sidebar?.isVisible) {
199227
return null;
200228
}
201229

202230
return (
203231
<PureSidebar
204-
items={items}
232+
items={processedItems}
205233
open={isOpen}
206234
openUserSelected={isUserOpened}
207235
isNarrowOnly={isNarrowOnly}
@@ -220,7 +248,7 @@ export interface PureSidebarProps {
220248
/** If the user has selected to open/shrink the sidebar */
221249
openUserSelected?: boolean;
222250
/** To show in the sidebar. */
223-
items: SidebarEntry[];
251+
items: SidebarItemProps[];
224252
/** The selected route name of the sidebar open. */
225253
selectedName: string | null;
226254
/** If the sidebar is the temporary one (full sidebar when user selects it in mobile). */
@@ -240,7 +268,6 @@ export const PureSidebar = memo(
240268
open,
241269
openUserSelected,
242270
items,
243-
selectedName,
244271
isTemporaryDrawer = false,
245272
isNarrowOnly = false,
246273
onToggleOpen,
@@ -292,7 +319,7 @@ export const PureSidebar = memo(
292319
{items.map(item => (
293320
<SidebarItem
294321
key={item.name}
295-
selectedName={selectedName}
322+
isSelected={item.isSelected}
296323
fullWidth={largeSideBarOpen}
297324
search={search}
298325
{...item}

frontend/src/components/Sidebar/SidebarItem.tsx

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import { SidebarEntry } from './sidebarSlice';
1313
* Adds onto SidebarEntryProps for the display of the sidebars.
1414
*/
1515
export interface SidebarItemProps extends ListItemProps, SidebarEntry {
16-
/** The route name which is selected. */
17-
selectedName?: string | null;
16+
/** Whether this item is selected. */
17+
isSelected?: boolean;
1818
/** The navigation is a child. */
1919
hasParent?: boolean;
2020
/** Displayed wide with icon and text, otherwise with just a small icon. */
@@ -36,7 +36,7 @@ const SidebarItem = memo((props: SidebarItemProps) => {
3636
search,
3737
useClusterURL = false,
3838
subList = [],
39-
selectedName,
39+
isSelected,
4040
hasParent = false,
4141
icon,
4242
fullWidth = true,
@@ -61,31 +61,6 @@ const SidebarItem = memo((props: SidebarItemProps) => {
6161
fullURL = createRouteURL(routeName);
6262
}
6363

64-
const isSelected = React.useMemo(() => {
65-
if (name === selectedName) {
66-
return true;
67-
}
68-
69-
let subListToCheck = [...subList];
70-
for (let i = 0; i < subListToCheck.length; i++) {
71-
const subItem = subListToCheck[i];
72-
if (subItem.name === selectedName) {
73-
return true;
74-
}
75-
76-
if (!!subItem.subList) {
77-
subListToCheck = subListToCheck.concat(subItem.subList);
78-
}
79-
}
80-
return false;
81-
}, [subList, name, selectedName]);
82-
83-
function shouldExpand() {
84-
return isSelected || !!subList.find(item => item.name === selectedName);
85-
}
86-
87-
const expanded = subList.length > 0 && shouldExpand();
88-
8964
return hide ? null : (
9065
<React.Fragment>
9166
<ListItemLink
@@ -221,7 +196,7 @@ const SidebarItem = memo((props: SidebarItemProps) => {
221196
padding: 0,
222197
}}
223198
>
224-
<Collapse in={fullWidth && expanded} sx={{ width: '100%' }}>
199+
<Collapse in={fullWidth && isSelected} sx={{ width: '100%' }}>
225200
<List
226201
component="ul"
227202
disablePadding
@@ -236,7 +211,7 @@ const SidebarItem = memo((props: SidebarItemProps) => {
236211
{subList.map((item: SidebarItemProps) => (
237212
<SidebarItem
238213
key={item.name}
239-
selectedName={selectedName}
214+
isSelected={item.isSelected}
240215
hasParent
241216
search={search}
242217
{...item}

frontend/src/components/Sidebar/Sidebaritem.stories.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const Template: StoryFn<SidebarItemProps> = args => {
3434

3535
export const Selected = Template.bind({});
3636
Selected.args = {
37-
selectedName: 'cluster',
37+
isSelected: true,
3838
name: 'cluster',
3939
label: 'Cluster',
4040
icon: 'mdi:hexagon-multiple-outline',
@@ -43,7 +43,7 @@ Selected.args = {
4343

4444
export const Unselected = Template.bind({});
4545
Unselected.args = {
46-
selectedName: 'meow',
46+
isSelected: false,
4747
name: 'cluster',
4848
label: 'Cluster',
4949
icon: 'mdi:hexagon-multiple-outline',
@@ -52,14 +52,14 @@ Unselected.args = {
5252

5353
export const SublistExpanded = Template.bind({});
5454
SublistExpanded.args = {
55-
selectedName: 'cluster',
55+
isSelected: true,
5656
name: 'cluster',
5757
label: 'Cluster',
5858
fullWidth: true,
5959
icon: 'mdi:hexagon-multiple-outline',
6060
subList: [
6161
{
62-
selectedName: 'cluster',
62+
isSelected: false,
6363
name: 'namespaces',
6464
label: 'Namespaces',
6565
hasParent: true,
@@ -69,14 +69,14 @@ SublistExpanded.args = {
6969

7070
export const Sublist = Template.bind({});
7171
Sublist.args = {
72-
selectedName: 'meow',
72+
isSelected: false,
7373
name: 'cluster',
7474
label: 'Cluster',
7575
fullWidth: true,
7676
icon: 'mdi:hexagon-multiple-outline',
7777
subList: [
7878
{
79-
selectedName: 'cluster',
79+
isSelected: false,
8080
name: 'namespaces',
8181
label: 'Namespaces',
8282
hasParent: true,

0 commit comments

Comments
 (0)