Fix blank stations page by waiting for auth initialization

- Add useIsAuthInitialized hook to auth-gate for reactive auth state
  - Returns true once auth token is acquired and ready
  - Waits for waitForAuthInit() promise to resolve

- Update useSavedStations hook to wait for auth before fetching
  - Add 'enabled: isAuthInitialized' to useQuery config
  - Prevents 401 errors from requests made before token is ready
  - Fixes race condition where hook fires before interceptor is set up

The stations page was blank because useSavedStations() made an API call
with refetchOnMount:true before the auth token interceptor was added,
causing a 401 response that made the component unmount/remount, creating
a React DOM error in the error boundary.

Now the hook waits for isAuthInitialized to be true before making the
initial API call, ensuring the token interceptor is ready.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2025-11-04 19:15:03 -06:00
parent 9a41a3c417
commit 0e8d8e7d5e
2 changed files with 29 additions and 1 deletions

View File

@@ -3,6 +3,8 @@
* @ai-context Prevents race conditions between IndexedDB init and API calls * @ai-context Prevents race conditions between IndexedDB init and API calls
*/ */
import { useState, useEffect } from 'react';
// Global authentication readiness state // Global authentication readiness state
let authInitialized = false; let authInitialized = false;
let authInitPromise: Promise<void> | null = null; let authInitPromise: Promise<void> | null = null;
@@ -99,4 +101,26 @@ const processRequestQueue = async () => {
isProcessingQueue = false; isProcessingQueue = false;
console.log('[Auth Gate] Finished processing queued requests'); console.log('[Auth Gate] Finished processing queued requests');
};
/**
* React hook to track auth initialization state
* Returns true once auth is fully initialized with token
*/
export const useIsAuthInitialized = () => {
const [initialized, setInitialized] = useState(authInitialized);
useEffect(() => {
if (authInitialized) {
setInitialized(true);
return;
}
// Wait for auth to initialize
waitForAuthInit().then(() => {
setInitialized(true);
});
}, []);
return initialized;
}; };

View File

@@ -3,6 +3,7 @@
*/ */
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useIsAuthInitialized } from '../../../core/auth/auth-gate';
import { stationsApi } from '../api/stations.api'; import { stationsApi } from '../api/stations.api';
import { SavedStation } from '../types/stations.types'; import { SavedStation } from '../types/stations.types';
@@ -33,12 +34,15 @@ interface UseSavedStationsOptions {
* ``` * ```
*/ */
export function useSavedStations(options?: UseSavedStationsOptions) { export function useSavedStations(options?: UseSavedStationsOptions) {
const isAuthInitialized = useIsAuthInitialized();
return useQuery({ return useQuery({
queryKey: SAVED_STATIONS_QUERY_KEY, queryKey: SAVED_STATIONS_QUERY_KEY,
queryFn: () => stationsApi.getSavedStations(), queryFn: () => stationsApi.getSavedStations(),
staleTime: options?.staleTime ?? 5 * 60 * 1000, // 5 minutes default staleTime: options?.staleTime ?? 5 * 60 * 1000, // 5 minutes default
refetchOnWindowFocus: options?.refetchOnWindowFocus ?? true, refetchOnWindowFocus: options?.refetchOnWindowFocus ?? true,
refetchOnMount: true refetchOnMount: true,
enabled: isAuthInitialized // Only fetch once auth is initialized
}); });
} }