veza/apps/web/src/hooks/useRoutePreload.ts

238 lines
5.7 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import { logger } from '@/utils/logger';
/**
* Hook pour précharger les routes
* @param routePath - Le chemin de la route à précharger
* @param delay - Délai avant le préchargement (ms)
*/
export function useRoutePreload(routePath: string, delay: number = 0) {
const [isPreloading, setIsPreloading] = useState(false);
const [isPreloaded, setIsPreloaded] = useState(false);
const preloadRoute = useCallback(async () => {
if (isPreloaded) return;
setIsPreloading(true);
try {
// Simuler le préchargement de la route
await new Promise((resolve) => setTimeout(resolve, delay));
// Ici, vous pourriez importer dynamiquement le composant
// const component = await import(`@/pages${routePath}`);
setIsPreloaded(true);
} catch (error) {
logger.error('Error preloading route', {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
routePath,
});
} finally {
setIsPreloading(false);
}
}, [routePath, delay, isPreloaded]);
return { preloadRoute, isPreloading, isPreloaded };
}
/**
* Hook pour gérer les performances et optimisations
* @returns Fonctions d'optimisation
*/
export function usePerformanceOptimization() {
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
const handleVisibilityChange = () => {
setIsVisible(!document.hidden);
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
const optimizeForVisibility = useCallback(
(callback: () => void) => {
if (isVisible) {
callback();
}
},
[isVisible],
);
const throttle = useCallback(
<T extends (...args: any[]) => any>(func: T, delay: number): T => {
let timeoutId: NodeJS.Timeout | null = null;
let lastExecTime = 0;
return ((...args: Parameters<T>) => {
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func(...args);
lastExecTime = currentTime;
} else {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(
() => {
func(...args);
lastExecTime = Date.now();
},
delay - (currentTime - lastExecTime),
);
}
}) as T;
},
[],
);
const debounce = useCallback(
<T extends (...args: any[]) => any>(func: T, delay: number): T => {
let timeoutId: NodeJS.Timeout | null = null;
return ((...args: Parameters<T>) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => func(...args), delay);
}) as T;
},
[],
);
return {
isVisible,
optimizeForVisibility,
throttle,
debounce,
};
}
/**
* Hook pour gérer les erreurs de manière centralisée
* @param onError - Callback appelé en cas d'erreur
* @returns Fonctions de gestion d'erreur
*/
export function useErrorHandler(
onError?: (error: Error, errorInfo?: any) => void,
) {
const [error, setError] = useState<Error | null>(null);
const handleError = useCallback(
(error: Error, errorInfo?: any) => {
setError(error);
onError?.(error, errorInfo);
// Log l'erreur pour le monitoring
logger.error('Error caught by useErrorHandler', {
error: error.message,
stack: error.stack,
errorInfo,
});
},
[onError],
);
const clearError = useCallback(() => {
setError(null);
}, []);
const withErrorHandling = useCallback(
<T extends (...args: any[]) => any>(fn: T): T => {
return ((...args: Parameters<T>) => {
try {
const result = fn(...args);
// Si c'est une Promise, gérer les erreurs asynchrones
if (result instanceof Promise) {
return result.catch((error) => {
handleError(error);
throw error;
});
}
return result;
} catch (error) {
handleError(error as Error);
throw error;
}
}) as T;
},
[handleError],
);
return {
error,
handleError,
clearError,
withErrorHandling,
};
}
/**
* Hook pour gérer les états de chargement
* @param initialState - État initial
* @returns Fonctions de gestion des états
*/
export function useLoadingState(initialState: boolean = false) {
const [isLoading, setIsLoading] = useState(initialState);
const [loadingStates, setLoadingStates] = useState<Record<string, boolean>>(
{},
);
const setLoading = useCallback((loading: boolean) => {
setIsLoading(loading);
}, []);
const setLoadingFor = useCallback((key: string, loading: boolean) => {
setLoadingStates((prev) => ({
...prev,
[key]: loading,
}));
}, []);
const isLoadingFor = useCallback(
(key: string) => {
return loadingStates[key] || false;
},
[loadingStates],
);
const withLoading = useCallback(
async <T>(asyncFn: () => Promise<T>, key?: string): Promise<T> => {
try {
if (key) {
setLoadingFor(key, true);
} else {
setLoading(true);
}
const result = await asyncFn();
return result;
} finally {
if (key) {
setLoadingFor(key, false);
} else {
setLoading(false);
}
}
},
[setLoading, setLoadingFor],
);
return {
isLoading,
loadingStates,
setLoading,
setLoadingFor,
isLoadingFor,
withLoading,
};
}