fix(app-shell): instabile Zustands-Selektoren veroursachten haengende Lazy-Routen ("Dashboard loads forever") — Root Cause: moduleMenuOrder()/visibleModuleKeys() erzeugten bei jedem getSnapshot neue Map/Set-Objekte
This commit is contained in:
@@ -73,7 +73,16 @@ export function Sidebar() {
|
|||||||
const { hasPermission } = usePermission();
|
const { hasPermission } = usePermission();
|
||||||
const user = useAuthStore((state) => state.user);
|
const user = useAuthStore((state) => state.user);
|
||||||
const { isModuleVisible } = useWorkspace();
|
const { isModuleVisible } = useWorkspace();
|
||||||
const moduleMenuOrder = useWorkspaceStore(s => s.moduleMenuOrder());
|
// NOTE: never select object-factory calls in a Zustand selector —
|
||||||
|
// `s.moduleMenuOrder()` creates a NEW Map on every getSnapshot call,
|
||||||
|
// which destabilizes useSyncExternalStore and causes a synchronous
|
||||||
|
// re-render loop that starves pending Suspense (lazy route) commits
|
||||||
|
// ("Dashboard loads forever" root cause). Select stable state and
|
||||||
|
// derive with useMemo instead.
|
||||||
|
const workspaceContext = useWorkspaceStore(s => s.context);
|
||||||
|
const moduleMenuOrder = useMemo(() => new Map(
|
||||||
|
(workspaceContext?.modules ?? []).map(m => [m.module_key, m.menu_order] as const),
|
||||||
|
), [workspaceContext]);
|
||||||
|
|
||||||
// Use hasPermission directly — permissions are loaded via useUserPermissions hook
|
// Use hasPermission directly — permissions are loaded via useUserPermissions hook
|
||||||
const canAccess = (perm?: string): boolean => {
|
const canAccess = (perm?: string): boolean => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useCallback } from 'react';
|
import { useEffect, useCallback, useMemo } from 'react';
|
||||||
import { useMyWorkspaces, useWorkspaceContext } from '@/api/hooks/workspaces';
|
import { useMyWorkspaces, useWorkspaceContext } from '@/api/hooks/workspaces';
|
||||||
import { useWorkspaceStore, type WorkspaceInfo, type WorkspaceContextState } from '@/store/workspaceStore';
|
import { useWorkspaceStore, type WorkspaceInfo, type WorkspaceContextState } from '@/store/workspaceStore';
|
||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
@@ -62,7 +62,13 @@ export function useWorkspace() {
|
|||||||
return isModuleVisibleFromStore(moduleKey, isSystemAdmin);
|
return isModuleVisibleFromStore(moduleKey, isSystemAdmin);
|
||||||
}, [isModuleVisibleFromStore, isSystemAdmin]);
|
}, [isModuleVisibleFromStore, isSystemAdmin]);
|
||||||
|
|
||||||
const visibleModuleKeys = useWorkspaceStore(s => s.visibleModuleKeys());
|
// NOTE: derive from the stable `context` reference via useMemo —
|
||||||
|
// `s.visibleModuleKeys()` would create a NEW Set on every getSnapshot
|
||||||
|
// call, destabilizing useSyncExternalStore (same root cause as the
|
||||||
|
// Sidebar moduleMenuOrder infinite re-render loop).
|
||||||
|
const visibleModuleKeys = useMemo(() => new Set(
|
||||||
|
(context?.modules ?? []).filter(m => m.is_visible !== false).map(m => m.module_key),
|
||||||
|
), [context]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeWorkspaceId,
|
activeWorkspaceId,
|
||||||
|
|||||||
@@ -94,8 +94,6 @@ const router = createBrowserRouter([
|
|||||||
path: '/login',
|
path: '/login',
|
||||||
element: <LoginPage />,
|
element: <LoginPage />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/dms-standalone',
|
path: '/dms-standalone',
|
||||||
element: <ErrorBoundary>{withSuspense(<DmsStandalonePage />)}</ErrorBoundary>,
|
element: <ErrorBoundary>{withSuspense(<DmsStandalonePage />)}</ErrorBoundary>,
|
||||||
|
|||||||
Reference in New Issue
Block a user