Phase 0: Fix infinite loop in AppSidebar and module settings loading

- Fixed infinite loop by memoizing moduleEnabled with useCallback
- Fixed useEffect dependencies to prevent re-render loops
- Added loading check to prevent duplicate API calls
- Fixed setState calls to only update when values actually change
- Removed unused import (isModuleEnabled from modules.config)
This commit is contained in:
IGNY8 VPS (Salman)
2025-11-16 19:13:12 +00:00
parent ab6b6cc4be
commit f195b6a72a
2 changed files with 33 additions and 21 deletions

Binary file not shown.

View File

@@ -21,7 +21,6 @@ import SidebarWidget from "./SidebarWidget";
import { APP_VERSION } from "../config/version"; import { APP_VERSION } from "../config/version";
import { useAuthStore } from "../store/authStore"; import { useAuthStore } from "../store/authStore";
import { useSettingsStore } from "../store/settingsStore"; import { useSettingsStore } from "../store/settingsStore";
import { isModuleEnabled } from "../config/modules.config";
import ApiStatusIndicator from "../components/sidebar/ApiStatusIndicator"; import ApiStatusIndicator from "../components/sidebar/ApiStatusIndicator";
type NavItem = { type NavItem = {
@@ -40,7 +39,7 @@ const AppSidebar: React.FC = () => {
const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar(); const { isExpanded, isMobileOpen, isHovered, setIsHovered } = useSidebar();
const location = useLocation(); const location = useLocation();
const { user } = useAuthStore(); const { user } = useAuthStore();
const { moduleEnableSettings, isModuleEnabled: checkModuleEnabled } = useSettingsStore(); const { moduleEnableSettings, isModuleEnabled: checkModuleEnabled, loadModuleEnableSettings, loading: settingsLoading } = useSettingsStore();
// Show admin menu only for users in aws-admin account // Show admin menu only for users in aws-admin account
const isAwsAdminAccount = Boolean( const isAwsAdminAccount = Boolean(
@@ -48,11 +47,11 @@ const AppSidebar: React.FC = () => {
user?.role === 'developer' // Also show for developers as fallback user?.role === 'developer' // Also show for developers as fallback
); );
// Helper to check if module is enabled // Helper to check if module is enabled - memoized to prevent infinite loops
const moduleEnabled = (moduleName: string): boolean => { const moduleEnabled = useCallback((moduleName: string): boolean => {
if (!moduleEnableSettings) return true; // Default to enabled if not loaded if (!moduleEnableSettings) return true; // Default to enabled if not loaded
return checkModuleEnabled(moduleName); return checkModuleEnabled(moduleName);
}; }, [moduleEnableSettings, checkModuleEnabled]);
const [openSubmenu, setOpenSubmenu] = useState<{ const [openSubmenu, setOpenSubmenu] = useState<{
sectionIndex: number; sectionIndex: number;
@@ -68,6 +67,15 @@ const AppSidebar: React.FC = () => {
[location.pathname] [location.pathname]
); );
// Load module enable settings on mount (only once)
useEffect(() => {
if (!moduleEnableSettings && !settingsLoading) {
loadModuleEnableSettings().catch((error) => {
console.warn('Failed to load module enable settings:', error);
});
}
}, []); // Empty dependency array - only run on mount
// Define menu sections with useMemo to prevent recreation on every render // Define menu sections with useMemo to prevent recreation on every render
// Filter out disabled modules based on module enable settings // Filter out disabled modules based on module enable settings
const menuSections: MenuSection[] = useMemo(() => { const menuSections: MenuSection[] = useMemo(() => {
@@ -196,7 +204,7 @@ const AppSidebar: React.FC = () => {
], ],
}, },
]; ];
}, [moduleEnableSettings, moduleEnabled]); }, [moduleEnabled]);
// Admin section - only shown for users in aws-admin account // Admin section - only shown for users in aws-admin account
const adminSection: MenuSection = useMemo(() => ({ const adminSection: MenuSection = useMemo(() => ({
@@ -282,14 +290,6 @@ const AppSidebar: React.FC = () => {
: menuSections; : menuSections;
}, [isAwsAdminAccount, menuSections, adminSection]); }, [isAwsAdminAccount, menuSections, adminSection]);
// Load module enable settings on mount
useEffect(() => {
const { loadModuleEnableSettings } = useSettingsStore.getState();
if (!moduleEnableSettings) {
loadModuleEnableSettings();
}
}, [moduleEnableSettings]);
useEffect(() => { useEffect(() => {
const currentPath = location.pathname; const currentPath = location.pathname;
let foundMatch = false; let foundMatch = false;
@@ -305,9 +305,15 @@ const AppSidebar: React.FC = () => {
}); });
if (shouldOpen) { if (shouldOpen) {
setOpenSubmenu({ setOpenSubmenu((prev) => {
sectionIndex, // Only update if different to prevent infinite loops
itemIndex, if (prev?.sectionIndex === sectionIndex && prev?.itemIndex === itemIndex) {
return prev;
}
return {
sectionIndex,
itemIndex,
};
}); });
foundMatch = true; foundMatch = true;
} }
@@ -330,10 +336,16 @@ const AppSidebar: React.FC = () => {
// scrollHeight should work even when height is 0px due to overflow-hidden // scrollHeight should work even when height is 0px due to overflow-hidden
const scrollHeight = element.scrollHeight; const scrollHeight = element.scrollHeight;
if (scrollHeight > 0) { if (scrollHeight > 0) {
setSubMenuHeight((prevHeights) => ({ setSubMenuHeight((prevHeights) => {
...prevHeights, // Only update if height changed to prevent infinite loops
[key]: scrollHeight, if (prevHeights[key] === scrollHeight) {
})); return prevHeights;
}
return {
...prevHeights,
[key]: scrollHeight,
};
});
} }
} }
}, 50); }, 50);