Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2025-02-12 - [IconifyIcon Lookups Unnecessarily Slow]
**Learning:** The custom `IconifyIcon` component iterated through all `iconSets` arrays looking for an icon matches on every render, resulting in O(N) operations. While fast in isolation, rendering hundreds of icons per page compounded this issue drastically slowing down page rendering.
**Action:** Always verify frequently invoked render functions don't iterate arrays to look for exact matches. We implemented a memory cache in `IconifyIcon` to store positive and negative results to convert lookup from O(N) to O(1) saving CPU cycles and speeding up rendering.
21 changes: 19 additions & 2 deletions client/src/components/base/IconifyIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,35 @@ const iconSets: Record<string, IconifyJSON> = {
"mdi-light": mdiLightIcons,
};

// Cache icon lookups to prevent O(N) array iteration on every render
const iconCache = new Map<string, any>();

const iconData = (icon: string) => {
if (iconCache.has(icon)) {
return iconCache.get(icon);
}

const [prefix, name] = icon.includes(":") ? icon.split(":") : ["", icon];

if (prefix && iconSets[prefix]) {
const data = getIconData(iconSets[prefix], name);
if (data) return data;
if (data) {
iconCache.set(icon, data);
return data;
}
}

for (const [_, icons] of Object.entries(iconSets)) {
const data = getIconData(icons, name);
if (data) return data;
if (data) {
iconCache.set(icon, data);
return data;
}
}

// Cache negative results as null to satisfy types and avoid repeated misses
iconCache.set(icon, null);
return null;
};

const IconifyIcon = ({
Expand Down