fix: remove Set-based Zustand selectors causing infinite re-render loop

Zustand selectors returning new Set() on every call fail Object.is
equality check, triggering continuous re-renders. Replaced with
useMemo-derived Sets in consuming components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-20 21:32:13 -05:00
parent 0b6d5727c9
commit a29f3474a5
3 changed files with 18 additions and 17 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react'
import { useState, useEffect, useRef, useMemo } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import { Search, Plus, Loader2, Star, ChevronDown, ChevronLeft, ChevronRight, Sparkles, FolderTree, ListOrdered, Wrench } from 'lucide-react'
import { treesApi } from '@/api/trees'
@@ -8,7 +8,7 @@ import type { Session } from '@/types/session'
import { getTreeNavigatePath } from '@/lib/routing'
import { usePermissions } from '@/hooks/usePermissions'
import { useAuthStore } from '@/store/authStore'
import { usePinnedFlowsStore, selectPinnedTreeIds, selectPinLoadingTreeIds } from '@/store/pinnedFlowsStore'
import { usePinnedFlowsStore } from '@/store/pinnedFlowsStore'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { usePaginationParams } from '@/hooks/usePaginationParams'
import { useCachedQuota } from '@/hooks/useCachedQuota'
@@ -70,10 +70,15 @@ export function QuickStartPage() {
const pinnedItems = usePinnedFlowsStore((s) => s.items)
const pinnedIsLoading = usePinnedFlowsStore((s) => s.isLoading)
const loadPinned = usePinnedFlowsStore((s) => s.load)
const pinnedTreeIds = usePinnedFlowsStore(selectPinnedTreeIds)
const pinLoadingTreeIds = usePinnedFlowsStore(selectPinLoadingTreeIds)
const isMutatingByTreeId = usePinnedFlowsStore((s) => s.isMutatingByTreeId)
const togglePin = usePinnedFlowsStore((s) => s.toggle)
const pinnedTreeIds = useMemo(() => new Set(pinnedItems.map((f) => f.tree_id)), [pinnedItems])
const pinLoadingTreeIds = useMemo(
() => new Set(Object.entries(isMutatingByTreeId).filter(([, v]) => v).map(([k]) => k)),
[isMutatingByTreeId]
)
// Preferences
const { dashboardMyFlowsView, setDashboardMyFlowsView } = useUserPreferencesStore()

View File

@@ -1,4 +1,4 @@
import { useEffect, useState, useCallback } from 'react'
import { useEffect, useState, useCallback, useMemo } from 'react'
import { useNavigate, Link, useSearchParams } from 'react-router-dom'
import { Plus, X, RotateCcw, Play, ChevronDown, Sparkles, FolderTree, ListOrdered, Wrench } from 'lucide-react'
import { treesApi } from '@/api/trees'
@@ -17,7 +17,7 @@ import { cn, safeGetItem } from '@/lib/utils'
import { getSessionResumePath } from '@/lib/routing'
import { usePermissions } from '@/hooks/usePermissions'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { usePinnedFlowsStore, selectPinnedTreeIds, selectPinLoadingTreeIds } from '@/store/pinnedFlowsStore'
import { usePinnedFlowsStore } from '@/store/pinnedFlowsStore'
import { useCachedQuota } from '@/hooks/useCachedQuota'
import { AIFlowBuilderModal } from '@/components/ai-builder/AIFlowBuilderModal'
import { toast } from '@/lib/toast'
@@ -78,8 +78,13 @@ export function TreeLibraryPage() {
const { aiEnabled } = useCachedQuota()
// Pin store
const pinnedTreeIds = usePinnedFlowsStore(selectPinnedTreeIds)
const pinLoadingTreeIds = usePinnedFlowsStore(selectPinLoadingTreeIds)
const pinnedItems = usePinnedFlowsStore((s) => s.items)
const isMutatingByTreeId = usePinnedFlowsStore((s) => s.isMutatingByTreeId)
const pinnedTreeIds = useMemo(() => new Set(pinnedItems.map((f) => f.tree_id)), [pinnedItems])
const pinLoadingTreeIds = useMemo(
() => new Set(Object.entries(isMutatingByTreeId).filter(([, v]) => v).map(([k]) => k)),
[isMutatingByTreeId]
)
const togglePin = usePinnedFlowsStore((s) => s.toggle)
const loadPinned = usePinnedFlowsStore((s) => s.load)

View File

@@ -102,12 +102,3 @@ export const usePinnedFlowsStore = create<PinnedFlowsState>()((set, get) => ({
},
}))
export const selectPinnedTreeIds = (state: PinnedFlowsState): Set<string> =>
new Set(state.items.map((f) => f.tree_id))
export const selectPinLoadingTreeIds = (state: PinnedFlowsState): Set<string> =>
new Set(
Object.entries(state.isMutatingByTreeId)
.filter(([, v]) => v)
.map(([k]) => k)
)