Skip to main content

πŸ₯· Loading Spinner Assassination Recipe

Having problems with Loading Spinners - maybe when refreshing logged in pages - Follow the this guide to eliminate the issue πŸ₯·

Marc P Summers avatar
Written by Marc P Summers
Updated over 3 weeks ago

πŸ₯· Spinner Assassination Recipe

πŸ‘¨β€πŸ³ Prep Time: 5 minutes

πŸ› οΈ Tools Needed: Supabase, React, useContext, and caffeine


🧾 Ingredients

  • 1 AuthContext with user and initialized state

  • 1 getUserProfile() or similar async loader

  • 1 <ProtectedLayout> checking initialized before showing protected content

  • 1 Spinner component (to murder)
    ​


πŸ‘£ Step-by-Step Instructions

1. Create AuthContext.tsx Carefully

Ensure initialized is only set to true after:

  • the Supabase session is checked,

  • the user is set, and

  • any required async profile loading is done.

ts

useEffect(() => {

const init = async () => {

const { data: { session } } = await supabase.auth.getSession();

if (session?.user) {

setUser(session.user);

try {

// OPTIONAL: load or create user profile

await getUserProfile(session.user.id);

} catch (e) {

console.error("❌ Failed to load profile", e);

}

}

setInitialized(true); // βœ… Only after session + profile load

};

init();

const { data: listener } = supabase.auth.onAuthStateChange((event, session) => {

setUser(session?.user ?? null);

});

return () => {

listener?.subscription.unsubscribe();

};

}, []);


2. Update ProtectedLayout.tsx

Don’t render the app until initialized is true.

tsx

const ProtectedLayout: React.FC = () => {

const { user, initialized } = useAuth();

if (!initialized) return <Spinner />; // πŸŒ€ Wait patiently

if (!user) return <Navigate to="/" replace />; // πŸšͺKick out guests

return (

<div>

<Header />

<Outlet />

</div>

);

};

3. Bonus: Add a key to Protected Routes

To fully force rerender when initialized flips from false β†’ true:

tsx

<Route path="/app/*" element={<AppLayout key={initialized ? "ready" : "loading"} />} />


4. Confirm the Kill

Check your console on refresh. You should see:

yaml

CopyEdit

πŸ§ƒ Initializing auth...

πŸ”„ Auth event: SIGNED_IN

πŸ“‘ Loading or creating profile for: user-id

βœ… Auth initialized: true

πŸ”₯ Spinner gone!

If not β€” your spinner lives. Be ashamed. πŸ”ͺ


🧠 Pro Tip

Name the Spinner something like WaitAFreakingSecond.tsx to stay humble.


🏁 Result

  • βœ… No more false starts
    ​
    ​

  • βœ… Fast refreshes
    ​
    ​

  • βœ… Spinner dies honorably, as it should
    ​
    ​


// πŸ₯· Spinner Assassination Guide

// React + Supabase + AuthContext + ProtectedRoute

// ---- 1. AuthContext.tsx ----

import { createContext, useContext, useEffect, useState } from 'react';
import { supabase } from '@/lib/supabase';

const AuthContext = createContext({ user: null, initialized: false });

export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [initialized, setInitialized] = useState(false);

useEffect(() => {
const init = async () => {
console.log('πŸ§ƒ Initializing auth...');
const { data: { session } } = await supabase.auth.getSession();
if (session?.user) {
setUser(session.user);

try {
console.log('πŸ“‘ Loading or creating profile for:', session.user.id);
await getUserProfile(session.user.id); // Optional profile fetch
} catch (err) {
console.warn('⚠️ Failed to load profile:', err);
}
}
setInitialized(true);
};

init();

const { data: listener } = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});

return () => listener.subscription.unsubscribe();
}, []);

return (
<AuthContext.Provider value={{ user, initialized }}>
{children}
</AuthContext.Provider>
);
};

export const useAuth = () => useContext(AuthContext);

// ---- 2. ProtectedLayout.tsx ----

import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext';

const Spinner = () => (
<div className="flex h-screen items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" />
</div>
);

export const ProtectedLayout = () => {
const { user, initialized } = useAuth();
if (!initialized) return <Spinner />;
if (!user) return <Navigate to="/" replace />;

return (
<div className="min-h-screen bg-white">
<Outlet />
</div>
);
};

// ---- 3. App.tsx Route Fix ----

import { useAuth } from '@/contexts/AuthContext';

const App = () => {
const { initialized } = useAuth();
return (
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/app/*" element={<AppLayout key={initialized ? 'ready' : 'loading'} />} />
{/* other routes */}
</Routes>
</AuthProvider>
</BrowserRouter>
);
};

Did this answer your question?