Fix infinite navigation loop in CompanyGuard
- Add hasNavigatedRef to prevent multiple navigation calls - Move /opret-virksomhed route outside CompanyGuard in App.tsx - Reduce retry count and disable refetchOnWindowFocus to limit re-renders - Remove activeCompany from useEffect dependencies (use getState() instead) This fixes "Too many calls to Location or History APIs" error that caused the app to crash with DOMException. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
de235a3da7
commit
effb06fc44
3 changed files with 79 additions and 13 deletions
|
|
@ -1,18 +1,64 @@
|
|||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||
import { App as AntApp } from 'antd';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import AppRoutes from './routes';
|
||||
import AppLayout from './components/layout/AppLayout';
|
||||
import ProtectedRoute from './components/auth/ProtectedRoute';
|
||||
import CompanyGuard from './components/auth/CompanyGuard';
|
||||
|
||||
// Lazy load CompanySetupWizard
|
||||
const CompanySetupWizard = lazy(() => import('./pages/CompanySetupWizard'));
|
||||
|
||||
// Loading fallback component
|
||||
function PageLoader() {
|
||||
return (
|
||||
<Spin
|
||||
size="large"
|
||||
tip="Indlæser..."
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<div style={{ minHeight: 200 }} />
|
||||
</Spin>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<AntApp>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
{/* Wizard route OUTSIDE CompanyGuard to prevent navigation loop */}
|
||||
<Route
|
||||
path="/opret-virksomhed"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Suspense fallback={<PageLoader />}>
|
||||
<CompanySetupWizard />
|
||||
</Suspense>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* All other routes with full guard chain */}
|
||||
<Route
|
||||
path="/*"
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<CompanyGuard>
|
||||
<AppLayout>
|
||||
<AppRoutes />
|
||||
</AppLayout>
|
||||
</CompanyGuard>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</AntApp>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ReactNode, useEffect } from 'react';
|
||||
import { ReactNode, useEffect, useRef } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Spin, Typography } from 'antd';
|
||||
import { useMyCompanies } from '@/api/queries/companyQueries';
|
||||
|
|
@ -17,7 +17,8 @@ interface CompanyGuardProps {
|
|||
export default function CompanyGuard({ children }: CompanyGuardProps) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { setCompanies, setActiveCompany, activeCompany } = useCompanyStore();
|
||||
const { setCompanies, setActiveCompany } = useCompanyStore();
|
||||
const hasNavigatedRef = useRef(false);
|
||||
|
||||
const {
|
||||
data: companies,
|
||||
|
|
@ -27,6 +28,8 @@ export default function CompanyGuard({ children }: CompanyGuardProps) {
|
|||
} = useMyCompanies({
|
||||
staleTime: 5 * 60 * 1000,
|
||||
refetchOnMount: true,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
// Sync companies to store when loaded
|
||||
|
|
@ -34,32 +37,46 @@ export default function CompanyGuard({ children }: CompanyGuardProps) {
|
|||
if (companies && companies.length > 0) {
|
||||
setCompanies(companies);
|
||||
|
||||
// Get current value without adding to dependencies to avoid infinite loop
|
||||
const current = useCompanyStore.getState().activeCompany;
|
||||
|
||||
// Validate that activeCompany exists in API response
|
||||
const validCompany = activeCompany
|
||||
? companies.find((c) => c.id === activeCompany.id)
|
||||
const validCompany = current
|
||||
? companies.find((c) => c.id === current.id)
|
||||
: null;
|
||||
|
||||
if (!validCompany) {
|
||||
// Persisted company doesn't exist - set to first
|
||||
setActiveCompany(companies[0]);
|
||||
} else if (JSON.stringify(validCompany) !== JSON.stringify(activeCompany)) {
|
||||
} else if (JSON.stringify(validCompany) !== JSON.stringify(current)) {
|
||||
// Update with fresh data from API
|
||||
setActiveCompany(validCompany);
|
||||
}
|
||||
}
|
||||
}, [companies, activeCompany, setCompanies, setActiveCompany]);
|
||||
}, [companies, setCompanies, setActiveCompany]);
|
||||
|
||||
// Redirect to wizard if no companies and not already on wizard page
|
||||
useEffect(() => {
|
||||
// Guard against multiple navigations to prevent infinite loop
|
||||
if (hasNavigatedRef.current) return;
|
||||
|
||||
if (!isLoading && companies !== undefined) {
|
||||
const isOnWizard = location.pathname === '/opret-virksomhed';
|
||||
|
||||
if (companies.length === 0 && !isOnWizard) {
|
||||
hasNavigatedRef.current = true;
|
||||
navigate('/opret-virksomhed', { replace: true });
|
||||
}
|
||||
// Note: Users with existing companies CAN access the wizard to create more
|
||||
}
|
||||
}, [companies, isLoading, location.pathname, navigate]);
|
||||
}, [companies, isLoading, navigate, location.pathname]);
|
||||
|
||||
// Reset navigation ref when companies change (user created a company)
|
||||
useEffect(() => {
|
||||
if (companies && companies.length > 0) {
|
||||
hasNavigatedRef.current = false;
|
||||
}
|
||||
}, [companies]);
|
||||
|
||||
// Show loading state
|
||||
if (isLoading) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const Eksport = lazy(() => import('./pages/Eksport'));
|
|||
const Settings = lazy(() => import('./pages/Settings'));
|
||||
const UserSettings = lazy(() => import('./pages/UserSettings'));
|
||||
const Admin = lazy(() => import('./pages/Admin'));
|
||||
// CompanySetupWizard moved to App.tsx (outside CompanyGuard)
|
||||
|
||||
// Invoicing pages
|
||||
const Kunder = lazy(() => import('./pages/Kunder'));
|
||||
|
|
@ -73,6 +74,8 @@ export default function AppRoutes() {
|
|||
{/* Admin */}
|
||||
<Route path="/admin" element={<Admin />} />
|
||||
|
||||
{/* Company setup wizard is now in App.tsx (outside CompanyGuard) */}
|
||||
|
||||
{/* Fallback redirect */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue