"use client"; import { useAtom } from "jotai"; import { useCallback, useEffect, useRef } from "react"; import { globalLoadingAtom } from "@/atoms/ui/loading.atoms"; // Global counter to generate unique IDs for each loading request let loadingIdCounter = 0; // Track the current active loading ID globally let currentLoadingId: number | null = null; // Pending hide timeout - allows new loading requests to take over before hiding let pendingHideTimeout: ReturnType | null = null; /** * Hook to control the global loading screen. * The spinner is always mounted in the DOM to prevent animation reset. */ export function useGlobalLoading() { const [loading, setLoading] = useAtom(globalLoadingAtom); const show = useCallback( (message?: string, variant: "login" | "default" = "default") => { // Cancel any pending hide - new loading request takes over if (pendingHideTimeout) { clearTimeout(pendingHideTimeout); pendingHideTimeout = null; } const id = ++loadingIdCounter; currentLoadingId = id; setLoading({ isLoading: true, message, variant }); return id; }, [setLoading] ); const hide = useCallback( (id?: number) => { // Only hide if this is the current loading, or if no ID provided (force hide) if (id === undefined || id === currentLoadingId) { // Use a small delay to allow React to flush pending mounts // This prevents flash when transitioning between loading states if (pendingHideTimeout) { clearTimeout(pendingHideTimeout); } pendingHideTimeout = setTimeout(() => { // Double-check we're still the current loading after the delay if (id === undefined || id === currentLoadingId) { currentLoadingId = null; setLoading({ isLoading: false, message: undefined, variant: "default" }); } pendingHideTimeout = null; }, 50); // Small delay to allow next component to mount and show loading } }, [setLoading] ); return { show, hide, isLoading: loading.isLoading }; } /** * Hook that automatically shows/hides the global loading screen based on a condition. * Useful for components that show loading on mount and hide on unmount. * * Uses ownership tracking to prevent flashes when multiple components * transition loading states (e.g., layout → page). * * @param shouldShow - Whether the loading screen should be visible * @param message - Optional message to display * @param variant - Visual style variant ("login" or "default") */ export function useGlobalLoadingEffect( shouldShow: boolean, message?: string, variant: "login" | "default" = "default" ) { const { show, hide } = useGlobalLoading(); const loadingIdRef = useRef(null); useEffect(() => { if (shouldShow) { // Show loading and store the ID loadingIdRef.current = show(message, variant); } else if (loadingIdRef.current !== null) { // Only hide if we were the ones showing loading hide(loadingIdRef.current); loadingIdRef.current = null; } }, [shouldShow, message, variant, show, hide]); // Cleanup on unmount - only hide if we're still the active loading useEffect(() => { return () => { if (loadingIdRef.current !== null) { hide(loadingIdRef.current); loadingIdRef.current = null; } }; }, [hide]); }