diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..8ead340 --- /dev/null +++ b/.jules/bolt.md @@ -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. diff --git a/client/src/components/base/IconifyIcon.tsx b/client/src/components/base/IconifyIcon.tsx index b48f0dc..c2d4928 100644 --- a/client/src/components/base/IconifyIcon.tsx +++ b/client/src/components/base/IconifyIcon.tsx @@ -33,18 +33,35 @@ const iconSets: Record = { "mdi-light": mdiLightIcons, }; +// Cache icon lookups to prevent O(N) array iteration on every render +const iconCache = new Map(); + 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 = ({