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 { App as AntApp } from 'antd';
|
||||||
|
import { Suspense, lazy } from 'react';
|
||||||
|
import { Spin } from 'antd';
|
||||||
import AppRoutes from './routes';
|
import AppRoutes from './routes';
|
||||||
import AppLayout from './components/layout/AppLayout';
|
import AppLayout from './components/layout/AppLayout';
|
||||||
import ProtectedRoute from './components/auth/ProtectedRoute';
|
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() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<AntApp>
|
<AntApp>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<ProtectedRoute>
|
<Routes>
|
||||||
<AppLayout>
|
{/* Wizard route OUTSIDE CompanyGuard to prevent navigation loop */}
|
||||||
<AppRoutes />
|
<Route
|
||||||
</AppLayout>
|
path="/opret-virksomhed"
|
||||||
</ProtectedRoute>
|
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>
|
</BrowserRouter>
|
||||||
</AntApp>
|
</AntApp>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ReactNode, useEffect } from 'react';
|
import { ReactNode, useEffect, useRef } from 'react';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { Spin, Typography } from 'antd';
|
import { Spin, Typography } from 'antd';
|
||||||
import { useMyCompanies } from '@/api/queries/companyQueries';
|
import { useMyCompanies } from '@/api/queries/companyQueries';
|
||||||
|
|
@ -17,7 +17,8 @@ interface CompanyGuardProps {
|
||||||
export default function CompanyGuard({ children }: CompanyGuardProps) {
|
export default function CompanyGuard({ children }: CompanyGuardProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { setCompanies, setActiveCompany, activeCompany } = useCompanyStore();
|
const { setCompanies, setActiveCompany } = useCompanyStore();
|
||||||
|
const hasNavigatedRef = useRef(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: companies,
|
data: companies,
|
||||||
|
|
@ -27,6 +28,8 @@ export default function CompanyGuard({ children }: CompanyGuardProps) {
|
||||||
} = useMyCompanies({
|
} = useMyCompanies({
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
|
retry: 1,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sync companies to store when loaded
|
// Sync companies to store when loaded
|
||||||
|
|
@ -34,32 +37,46 @@ export default function CompanyGuard({ children }: CompanyGuardProps) {
|
||||||
if (companies && companies.length > 0) {
|
if (companies && companies.length > 0) {
|
||||||
setCompanies(companies);
|
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
|
// Validate that activeCompany exists in API response
|
||||||
const validCompany = activeCompany
|
const validCompany = current
|
||||||
? companies.find((c) => c.id === activeCompany.id)
|
? companies.find((c) => c.id === current.id)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!validCompany) {
|
if (!validCompany) {
|
||||||
// Persisted company doesn't exist - set to first
|
// Persisted company doesn't exist - set to first
|
||||||
setActiveCompany(companies[0]);
|
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
|
// Update with fresh data from API
|
||||||
setActiveCompany(validCompany);
|
setActiveCompany(validCompany);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [companies, activeCompany, setCompanies, setActiveCompany]);
|
}, [companies, setCompanies, setActiveCompany]);
|
||||||
|
|
||||||
// Redirect to wizard if no companies and not already on wizard page
|
// Redirect to wizard if no companies and not already on wizard page
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Guard against multiple navigations to prevent infinite loop
|
||||||
|
if (hasNavigatedRef.current) return;
|
||||||
|
|
||||||
if (!isLoading && companies !== undefined) {
|
if (!isLoading && companies !== undefined) {
|
||||||
const isOnWizard = location.pathname === '/opret-virksomhed';
|
const isOnWizard = location.pathname === '/opret-virksomhed';
|
||||||
|
|
||||||
if (companies.length === 0 && !isOnWizard) {
|
if (companies.length === 0 && !isOnWizard) {
|
||||||
|
hasNavigatedRef.current = true;
|
||||||
navigate('/opret-virksomhed', { replace: true });
|
navigate('/opret-virksomhed', { replace: true });
|
||||||
}
|
}
|
||||||
// Note: Users with existing companies CAN access the wizard to create more
|
// 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
|
// Show loading state
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const Eksport = lazy(() => import('./pages/Eksport'));
|
||||||
const Settings = lazy(() => import('./pages/Settings'));
|
const Settings = lazy(() => import('./pages/Settings'));
|
||||||
const UserSettings = lazy(() => import('./pages/UserSettings'));
|
const UserSettings = lazy(() => import('./pages/UserSettings'));
|
||||||
const Admin = lazy(() => import('./pages/Admin'));
|
const Admin = lazy(() => import('./pages/Admin'));
|
||||||
|
// CompanySetupWizard moved to App.tsx (outside CompanyGuard)
|
||||||
|
|
||||||
// Invoicing pages
|
// Invoicing pages
|
||||||
const Kunder = lazy(() => import('./pages/Kunder'));
|
const Kunder = lazy(() => import('./pages/Kunder'));
|
||||||
|
|
@ -73,6 +74,8 @@ export default function AppRoutes() {
|
||||||
{/* Admin */}
|
{/* Admin */}
|
||||||
<Route path="/admin" element={<Admin />} />
|
<Route path="/admin" element={<Admin />} />
|
||||||
|
|
||||||
|
{/* Company setup wizard is now in App.tsx (outside CompanyGuard) */}
|
||||||
|
|
||||||
{/* Fallback redirect */}
|
{/* Fallback redirect */}
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue