From dacd68b6177abec7f605d585d698fd051b156f60 Mon Sep 17 00:00:00 2001 From: Max Amundsen Date: Wed, 15 Jul 2026 13:01:18 -0400 Subject: [PATCH] reconsile cdrl error --- go/jsruntime/uikit/CrmTabs.tsx | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/go/jsruntime/uikit/CrmTabs.tsx b/go/jsruntime/uikit/CrmTabs.tsx index 1659c42e..d058ad40 100644 --- a/go/jsruntime/uikit/CrmTabs.tsx +++ b/go/jsruntime/uikit/CrmTabs.tsx @@ -1,4 +1,4 @@ -import { createSignal, onCleanup, onMount, Show, For, JSXElement } from "solid-js"; +import { createSignal, createMemo, onCleanup, onMount, Show, For, JSXElement } from "solid-js"; // CrmTabGroup / CrmSubTabGroup — drop-in, behaviourally identical siblings of // TabGroup (same props, storageKey syncing, controlled/uncontrolled index) with @@ -26,13 +26,15 @@ function resolveCrmBadge(badge: number | undefined): number | undefined { // Shared active-index state: mirrors TabGroup exactly (localStorage persistence, // cross-component sync via synthetic storage events, optional controlled index). -function createCrmTabState(props: CrmTabGroupProps) { +// Takes the memoized `items` accessor (not props.items) so length checks read the +// same single evaluation the render does — see the createMemo note in CrmTabGroup. +function createCrmTabState(props: CrmTabGroupProps, items: () => CrmTabItem[]) { const getInitialIndex = () => { if (props.storageKey) { const stored = localStorage.getItem(props.storageKey); if (stored !== null) { const parsed = parseInt(stored, 10); - if (!isNaN(parsed) && parsed >= 0 && parsed < props.items.length) { + if (!isNaN(parsed) && parsed >= 0 && parsed < items().length) { return parsed; } } @@ -65,7 +67,7 @@ function createCrmTabState(props: CrmTabGroupProps) { const handler = (e: StorageEvent) => { if (e.key !== props.storageKey || e.newValue == null) return; const n = parseInt(e.newValue, 10); - if (!isNaN(n) && n >= 0 && n < props.items.length && n !== _activeIndex()) { + if (!isNaN(n) && n >= 0 && n < items().length && n !== _activeIndex()) { _setActiveIndex(n); props.onTabChange && props.onTabChange(n); } @@ -103,11 +105,19 @@ const CRM_TAB_ACTIVE = "border-x border-t-2 border-t-sky-700 text-primary"; const CRM_TAB_BADGE = "inline-flex items-center justify-center min-w-5 h-5 px-1 text-xs font-semibold bg-primary text-white rounded-full"; export function CrmTabGroup(props: CrmTabGroupProps) { - const { activeIndex, setActiveIndex } = createCrmTabState(props); + // Memoize props.items: callers pass items={[{content: }]} where each + // `content` is eager JSX, and Solid re-evaluates a `prop={expr}` getter on every + // read. Reading props.items directly (in the button For, the content For, and the + // length checks reached from the onclick handler) would re-run createComponent on + // every tab's content each time — and the read inside setActiveIndex happens outside + // the render owner, so those rebuilt components lose their provider context + // (useToast/useModal throw). The memo evaluates the tree once, inside this owner. + const items = createMemo(() => props.items); + const { activeIndex, setActiveIndex } = createCrmTabState(props, items); return
- {(item: CrmTabItem, index: () => number) => ( + {(item: CrmTabItem, index: () => number) => (
- {crmTabContent(props.items, activeIndex)} + {crmTabContent(items(), activeIndex)}
; } @@ -141,12 +151,14 @@ const CRM_SUBTAB_INACTIVE = "bg-surface text-ink-soft hover:bg-surface-raised ho const CRM_SUBTAB_BADGE = "inline-flex items-center justify-center min-w-5 h-5 px-1 text-xs font-semibold bg-black/10 text-current rounded-full"; export function CrmSubTabGroup(props: CrmTabGroupProps) { - const { activeIndex, setActiveIndex } = createCrmTabState(props); + // See CrmTabGroup: memoize eager `content` JSX so it's built once, in-owner. + const items = createMemo(() => props.items); + const { activeIndex, setActiveIndex } = createCrmTabState(props, items); return
- {(item: CrmTabItem, index: () => number) => ( + {(item: CrmTabItem, index: () => number) => (
- {crmTabContent(props.items, activeIndex)} + {crmTabContent(items(), activeIndex)}
; }