From 3465b9f607609a235aff2a665ee47344ed3f7a5f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:59:58 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20memoize=20IconifyIcon=20ico?= =?UTF-8?q?nData=20resolution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a caching layer using a Map to `client/src/components/base/IconifyIcon.tsx` to prevent redundant O(N) array iteration lookups for every rendered icon. Cached both successful and failed lookups (using `null` instead of `undefined` to satisfy `ExtendedIconifyIcon` typings) and verified Next.js builds. Co-authored-by: sshahriazz <34005640+sshahriazz@users.noreply.github.com> --- .jules/bolt.md | 3 ++ client/src/components/base/IconifyIcon.tsx | 32 ++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..c23fa77 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2025-02-18 - IconifyIcon Component Next.js Cache Type Requirements +**Learning:** The custom `IconifyIcon` component (`client/src/components/base/IconifyIcon.tsx`) requires an O(N) array iteration to resolve icons from multiple loaded JSON icon sets. Caching these lookups is essential, especially caching negative results (missing icons). However, using `undefined` for a missing result cache breaks Next.js strict build validations since `@iconify/react`'s `IconProps["icon"]` implicitly expects an extended icon shape (`IconifyIcon & { [key: string]: any }`) or primitive types. +**Action:** When implementing a custom cache to memoize resolved Iconify icons from JSON imports inside Next.js components, define an explicit `ExtendedIconifyIcon | null` type. Store `null` instead of `undefined` for negative hits to satisfy `IconifyIcon | null` constraint without triggering bundle/build failures from `@iconify/react` type exports. diff --git a/client/src/components/base/IconifyIcon.tsx b/client/src/components/base/IconifyIcon.tsx index b48f0dc..b4d99c8 100644 --- a/client/src/components/base/IconifyIcon.tsx +++ b/client/src/components/base/IconifyIcon.tsx @@ -9,7 +9,7 @@ import { icons as mdiIcons } from "@iconify-json/mdi"; import { icons as mdiLightIcons } from "@iconify-json/mdi-light"; import { icons as riIcons } from "@iconify-json/ri"; import { icons as twemojiIcons } from "@iconify-json/twemoji"; -import { Icon, IconifyJSON, IconProps } from "@iconify/react"; +import { Icon, IconifyIcon as IconifyIconType, IconifyJSON, IconProps } from "@iconify/react"; import { getIconData } from "@iconify/utils"; import { Box, BoxProps } from "@mui/material"; @@ -33,18 +33,40 @@ const iconSets: Record = { "mdi-light": mdiLightIcons, }; +// ExtendedIconifyIcon constraint is required here to satisfy TypeScript build errors +// when using the cache for negative lookups (null) +type ExtendedIconifyIcon = IconifyIconType & { [key: string]: any }; +type IconDataCacheValue = ExtendedIconifyIcon | null; + +// Cache to prevent O(N) lookup loops across all imported icon sets for every icon 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; + const data = getIconData(iconSets[prefix], name) as ExtendedIconifyIcon | null; + if (data) { + iconCache.set(icon, data); + return data; + } } for (const [_, icons] of Object.entries(iconSets)) { - const data = getIconData(icons, name); - if (data) return data; + const data = getIconData(icons, name) as ExtendedIconifyIcon | null; + if (data) { + iconCache.set(icon, data); + return data; + } } + + // Cache missing icons as null to avoid repeated failing O(N) lookups + iconCache.set(icon, null); + return null; }; const IconifyIcon = ({