diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..ad4d339 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-06-01 - Icon Data Lookup Caching +**Learning:** The IconifyIcon component dynamically parses and resolves icon data (sometimes iterating over 11 icon sets for fallback lookups) on every render, which is expensive given the number of icons typically rendered in a dashboard. +**Action:** Always cache the results of expensive, static configuration lookups (like icon resolving) outside the render cycle or add memoization to prevent recalculating on every re-render. diff --git a/client/src/components/base/IconifyIcon.tsx b/client/src/components/base/IconifyIcon.tsx index b48f0dc..46ddb0c 100644 --- a/client/src/components/base/IconifyIcon.tsx +++ b/client/src/components/base/IconifyIcon.tsx @@ -33,17 +33,31 @@ const iconSets: Record = { "mdi-light": mdiLightIcons, }; +// Cache to store resolved icon data and prevent redundant lookups +// This avoids O(N) iteration over multiple icon sets for prefix-less icons on every render +const iconDataCache = new Map(); + const iconData = (icon: string) => { + if (iconDataCache.has(icon)) { + return iconDataCache.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) { + iconDataCache.set(icon, data); + return data; + } } for (const [_, icons] of Object.entries(iconSets)) { const data = getIconData(icons, name); - if (data) return data; + if (data) { + iconDataCache.set(icon, data); + return data; + } } };