Single Loading Guard for Many Providers Pattern

When your app depends on multiple async providers (Google Maps, session, etc.), use a single LoadingGuard to show a loading state until all of them are ready.

The LoadingGuard

// components/templates/LoadingGuard.tsx
'use client';

import { useSession } from 'next-auth/react';
import { useApiLoadingStatus } from '@vis.gl/react-google-maps';

export function LoadingGuard({ children }: { children: React.ReactNode }) {
  const { status: sessionStatus } = useSession();
  const mapsStatus = useApiLoadingStatus();

  const isLoading = sessionStatus === 'loading' || mapsStatus !== 'LOADED';

  if (isLoading) {
    return <LoadingIndicator />;
  }

  return <>{children}</>;
}

The guard checks both SessionProvider and APIProvider loading states. Children only render when everything is ready.

Usage in Layout

Place the guard inside the providers it checks:

// app/layout.tsx
'use client';

import { SessionProvider } from 'next-auth/react';
import { APIProvider } from '@vis.gl/react-google-maps';
import { LoadingGuard } from 'components/templates/LoadingGuard';

export default function Layout({ children }) {
  return (
    <APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!}>
      <SessionProvider>
        <LoadingGuard>{children}</LoadingGuard>
      </SessionProvider>
    </APIProvider>
  );
}

The guard must be inside APIProvider and SessionProvider to access their hooks.

Combining Provider and Loading Guard

You can combine a provider and its loading guard into a single component. This guarantees that children always have access to loaded data, eliminating null checks.

// auth/SessionLoadingGuard.tsx
import { SessionProvider } from 'next-auth/react';
import { usePermissions } from '@app-admin/auth/usePermissions';
import { LoadingCircular } from '@repo/ui/LoadingCircular';

export const SessionLoadingGuard = ({ children }: { children: React.ReactNode }) => {
  return (
    <SessionProvider>
      <ChildrenWrapper>{children}</ChildrenWrapper>
    </SessionProvider>
  );
};

const ChildrenWrapper = ({ children }: { children: React.ReactNode }) => {
  const { isLoading } = usePermissions();

  if (isLoading) {
    return <LoadingCircular />;
  }

  return <>{children}</>;
};

Now any component using useSession or usePermissions inside SessionLoadingGuard can assume the session is loaded:

// Before: null checks everywhere
const { data: session } = useSession();
if (!session) return null;
const userId = session.user.id;

// After: session is always available
const { data: session } = useSession();
const userId = session!.user.id;

This pattern moves the loading logic to the boundary, keeping child components simple.