fix infinite loop

This commit is contained in:
Danny White committed 2026-01-27 10:33:14 +10:00
1 parent f45c0ecadc
commit 84207c1b9d
2 files changed
+115 -19

No files matched your search

@@ -1,39 +1,126 @@
import { History } from 'lucide-react'
import { useRouter } from 'next/router'
import { useMemo } from 'react'
import { useCallback, useMemo } from 'react'
import { useParams } from 'common'
import { useRecentRoutes } from 'hooks/misc/useRecentRoutes'
import { useRegisterCommands, useSetCommandMenuOpen } from 'ui-patterns/CommandMenu'
import {
PageType,
useRegisterCommands,
useRegisterPage,
useSetCommandMenuOpen,
useSetPage,
} from 'ui-patterns/CommandMenu'
import { COMMAND_MENU_SECTIONS } from './CommandMenu.utils'
import { orderCommandSectionsByPriority } from './ordering'
const RECENTS_PAGE_NAME = 'Recent Pages'
const VISIBLE_RECENTS_COUNT = 3
export function useRecentRoutesCommands() {
const router = useRouter()
const { ref } = useParams()
const setIsOpen = useSetCommandMenuOpen()
const setPage = useSetPage()
const { recentRoutes } = useRecentRoutes()
const commands = useMemo(() => {
// Stable navigation function
const navigateTo = useCallback(
(link: string) => {
router.push(link)
setIsOpen(false)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
// Stable page navigation
const goToAllRecents = useCallback(() => {
setPage(RECENTS_PAGE_NAME)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// Create command objects from recent routes - only depend on the route data, not callbacks
const allCommands = useMemo(() => {
return recentRoutes.map((route) => ({
id: `recent-${route.key}`,
name: route.childName,
// Include parent label in value for search
value: route.parentLabel ? `${route.childName} ${route.parentLabel}` : route.childName,
action: () => {
router.push(route.link)
setIsOpen(false)
},
link: route.link,
parentLabel: route.parentLabel,
}))
}, [recentRoutes])
// Commands shown in the main menu (first 3)
const visibleCommands = useMemo(() => {
return allCommands.slice(0, VISIBLE_RECENTS_COUNT)
}, [allCommands])
// Convert to ICommand format with actions
const allCommandsWithActions = useMemo(() => {
return allCommands.map((cmd) => ({
id: cmd.id,
name: cmd.name,
value: cmd.value,
action: () => navigateTo(cmd.link),
icon: () => <History size={18} />,
badge: route.parentLabel
? () => <span className="text-xs text-foreground-muted">{route.parentLabel}</span>
badge: cmd.parentLabel
? () => <span className="text-xs text-foreground-muted">{cmd.parentLabel}</span>
: undefined,
}))
}, [recentRoutes, router, setIsOpen])
}, [allCommands, navigateTo])
useRegisterCommands(COMMAND_MENU_SECTIONS.RECENTS, commands, {
enabled: !!ref && commands.length > 0,
deps: [commands],
const visibleCommandsWithActions = useMemo(() => {
return visibleCommands.map((cmd) => ({
id: cmd.id,
name: cmd.name,
value: cmd.value,
action: () => navigateTo(cmd.link),
icon: () => <History size={18} />,
badge: cmd.parentLabel
? () => <span className="text-xs text-foreground-muted">{cmd.parentLabel}</span>
: undefined,
}))
}, [visibleCommands, navigateTo])
// Register the "All recents" subpage with all commands
useRegisterPage(
RECENTS_PAGE_NAME,
{
type: PageType.Commands,
sections: [
{
id: 'all-recents',
name: 'Recent Pages',
commands: allCommandsWithActions,
},
],
},
{ deps: [allCommandsWithActions], enabled: !!ref && allCommands.length > VISIBLE_RECENTS_COUNT }
)
// Register the visible commands + "All recents..." link
const commandsWithAllRecents = useMemo(() => {
const commands = [...visibleCommandsWithActions]
// Add "All recents..." command if there are more than visible
if (allCommands.length > VISIBLE_RECENTS_COUNT) {
commands.push({
id: 'all-recents',
name: 'All recents...',
value: 'All recents recent pages history',
action: goToAllRecents,
icon: () => <History size={18} />,
badge: undefined,
})
}
return commands
}, [visibleCommandsWithActions, allCommands.length, goToAllRecents])
useRegisterCommands(COMMAND_MENU_SECTIONS.RECENTS, commandsWithAllRecents, {
enabled: !!ref && commandsWithAllRecents.length > 0,
deps: [commandsWithAllRecents],
orderSection: orderCommandSectionsByPriority,
sectionMeta: { priority: 1 },
})
+14 -5
View File
@@ -1,5 +1,5 @@
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { useCallback, useMemo, useRef } from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useLocalStorageQuery } from './useLocalStorage'
export type RecentRoute = {
@@ -12,7 +12,7 @@ export type RecentRoute = {
visitedAt: number // Timestamp for ordering
}
const MAX_RECENT_ROUTES = 5
const MAX_RECENT_ROUTES = 15
const EXCLUDED_ROUTE_KEYS = ['HOME'] // Don't track Project Overview since it's always visible
const DEFAULT_RECENT_ROUTES: RecentRoute[] = []
@@ -20,12 +20,19 @@ const DEFAULT_RECENT_ROUTES: RecentRoute[] = []
export const useRecentRoutes = () => {
const { ref } = useParams()
const lastTrackedKeyRef = useRef<string | null>(null)
const recentRoutesRef = useRef<RecentRoute[]>(DEFAULT_RECENT_ROUTES)
const [recentRoutes, setRecentRoutes, { isSuccess }] = useLocalStorageQuery<RecentRoute[]>(
LOCAL_STORAGE_KEYS.RECENT_SIDEBAR_ROUTES(ref ?? ''),
DEFAULT_RECENT_ROUTES
)
// Keep ref in sync with latest routes
useEffect(() => {
recentRoutesRef.current = recentRoutes
}, [recentRoutes])
// Stable trackRoute that uses ref for current routes
const trackRoute = useCallback(
(route: {
key: string
@@ -51,13 +58,15 @@ export const useRecentRoutes = () => {
}
lastTrackedKeyRef.current = route.key
const currentRoutes = recentRoutesRef.current
// Check if this route is already at the top - if so, don't update
if (recentRoutes.length > 0 && recentRoutes[0].key === route.key) {
if (currentRoutes.length > 0 && currentRoutes[0].key === route.key) {
return
}
// Remove existing entry with the same key (to update visitedAt)
const filteredRoutes = recentRoutes.filter((r) => r.key !== route.key)
const filteredRoutes = currentRoutes.filter((r) => r.key !== route.key)
// Add new entry at the beginning
const newRoute: RecentRoute = {
@@ -74,7 +83,7 @@ export const useRecentRoutes = () => {
const newRoutes = [newRoute, ...filteredRoutes].slice(0, MAX_RECENT_ROUTES)
setRecentRoutes(newRoutes)
},
[recentRoutes, setRecentRoutes]
[setRecentRoutes]
)
// Sort by visitedAt descending (most recent first)