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:
@@ -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 { useNavigate, Link } from 'react-router-dom'
|
||||||
import { Search, Plus, Loader2, Star, ChevronDown, ChevronLeft, ChevronRight, Sparkles, FolderTree, ListOrdered, Wrench } from 'lucide-react'
|
import { Search, Plus, Loader2, Star, ChevronDown, ChevronLeft, ChevronRight, Sparkles, FolderTree, ListOrdered, Wrench } from 'lucide-react'
|
||||||
import { treesApi } from '@/api/trees'
|
import { treesApi } from '@/api/trees'
|
||||||
@@ -8,7 +8,7 @@ import type { Session } from '@/types/session'
|
|||||||
import { getTreeNavigatePath } from '@/lib/routing'
|
import { getTreeNavigatePath } from '@/lib/routing'
|
||||||
import { usePermissions } from '@/hooks/usePermissions'
|
import { usePermissions } from '@/hooks/usePermissions'
|
||||||
import { useAuthStore } from '@/store/authStore'
|
import { useAuthStore } from '@/store/authStore'
|
||||||
import { usePinnedFlowsStore, selectPinnedTreeIds, selectPinLoadingTreeIds } from '@/store/pinnedFlowsStore'
|
import { usePinnedFlowsStore } from '@/store/pinnedFlowsStore'
|
||||||
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
||||||
import { usePaginationParams } from '@/hooks/usePaginationParams'
|
import { usePaginationParams } from '@/hooks/usePaginationParams'
|
||||||
import { useCachedQuota } from '@/hooks/useCachedQuota'
|
import { useCachedQuota } from '@/hooks/useCachedQuota'
|
||||||
@@ -70,10 +70,15 @@ export function QuickStartPage() {
|
|||||||
const pinnedItems = usePinnedFlowsStore((s) => s.items)
|
const pinnedItems = usePinnedFlowsStore((s) => s.items)
|
||||||
const pinnedIsLoading = usePinnedFlowsStore((s) => s.isLoading)
|
const pinnedIsLoading = usePinnedFlowsStore((s) => s.isLoading)
|
||||||
const loadPinned = usePinnedFlowsStore((s) => s.load)
|
const loadPinned = usePinnedFlowsStore((s) => s.load)
|
||||||
const pinnedTreeIds = usePinnedFlowsStore(selectPinnedTreeIds)
|
const isMutatingByTreeId = usePinnedFlowsStore((s) => s.isMutatingByTreeId)
|
||||||
const pinLoadingTreeIds = usePinnedFlowsStore(selectPinLoadingTreeIds)
|
|
||||||
const togglePin = usePinnedFlowsStore((s) => s.toggle)
|
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
|
// Preferences
|
||||||
const { dashboardMyFlowsView, setDashboardMyFlowsView } = useUserPreferencesStore()
|
const { dashboardMyFlowsView, setDashboardMyFlowsView } = useUserPreferencesStore()
|
||||||
|
|
||||||
|
|||||||
@@ -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 { useNavigate, Link, useSearchParams } from 'react-router-dom'
|
||||||
import { Plus, X, RotateCcw, Play, ChevronDown, Sparkles, FolderTree, ListOrdered, Wrench } from 'lucide-react'
|
import { Plus, X, RotateCcw, Play, ChevronDown, Sparkles, FolderTree, ListOrdered, Wrench } from 'lucide-react'
|
||||||
import { treesApi } from '@/api/trees'
|
import { treesApi } from '@/api/trees'
|
||||||
@@ -17,7 +17,7 @@ import { cn, safeGetItem } from '@/lib/utils'
|
|||||||
import { getSessionResumePath } from '@/lib/routing'
|
import { getSessionResumePath } from '@/lib/routing'
|
||||||
import { usePermissions } from '@/hooks/usePermissions'
|
import { usePermissions } from '@/hooks/usePermissions'
|
||||||
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
||||||
import { usePinnedFlowsStore, selectPinnedTreeIds, selectPinLoadingTreeIds } from '@/store/pinnedFlowsStore'
|
import { usePinnedFlowsStore } from '@/store/pinnedFlowsStore'
|
||||||
import { useCachedQuota } from '@/hooks/useCachedQuota'
|
import { useCachedQuota } from '@/hooks/useCachedQuota'
|
||||||
import { AIFlowBuilderModal } from '@/components/ai-builder/AIFlowBuilderModal'
|
import { AIFlowBuilderModal } from '@/components/ai-builder/AIFlowBuilderModal'
|
||||||
import { toast } from '@/lib/toast'
|
import { toast } from '@/lib/toast'
|
||||||
@@ -78,8 +78,13 @@ export function TreeLibraryPage() {
|
|||||||
const { aiEnabled } = useCachedQuota()
|
const { aiEnabled } = useCachedQuota()
|
||||||
|
|
||||||
// Pin store
|
// Pin store
|
||||||
const pinnedTreeIds = usePinnedFlowsStore(selectPinnedTreeIds)
|
const pinnedItems = usePinnedFlowsStore((s) => s.items)
|
||||||
const pinLoadingTreeIds = usePinnedFlowsStore(selectPinLoadingTreeIds)
|
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 togglePin = usePinnedFlowsStore((s) => s.toggle)
|
||||||
const loadPinned = usePinnedFlowsStore((s) => s.load)
|
const loadPinned = usePinnedFlowsStore((s) => s.load)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user