Initial commit
This commit is contained in:
173
hooks/useApiCall.tsx
Normal file
173
hooks/useApiCall.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import { apiService } from '../services/api';
|
||||
|
||||
interface UseApiCallOptions {
|
||||
onSuccess?: (data: any) => void;
|
||||
onError?: (error: any) => void;
|
||||
showSuccessToast?: boolean;
|
||||
showErrorToast?: boolean;
|
||||
successMessage?: string;
|
||||
retryAttempts?: number;
|
||||
retryDelay?: number;
|
||||
}
|
||||
|
||||
interface UseApiCallReturn<T> {
|
||||
data: T | null;
|
||||
loading: boolean;
|
||||
error: any;
|
||||
execute: (...args: any[]) => Promise<T | null>;
|
||||
retry: () => Promise<T | null>;
|
||||
reset: () => void;
|
||||
toast: React.RefObject<Toast>;
|
||||
}
|
||||
|
||||
export function useApiCall<T = any>(
|
||||
apiFunction: (...args: any[]) => Promise<T>,
|
||||
options: UseApiCallOptions = {}
|
||||
): UseApiCallReturn<T> {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<any>(null);
|
||||
const toast = useRef<Toast>(null);
|
||||
const lastArgsRef = useRef<any[]>([]);
|
||||
const retryCountRef = useRef(0);
|
||||
|
||||
const {
|
||||
onSuccess,
|
||||
onError,
|
||||
showSuccessToast = false,
|
||||
showErrorToast = true,
|
||||
successMessage = 'Opération réussie',
|
||||
retryAttempts = 0,
|
||||
retryDelay = 1000
|
||||
} = options;
|
||||
|
||||
const execute = useCallback(async (...args: any[]): Promise<T | null> => {
|
||||
lastArgsRef.current = args;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
retryCountRef.current = 0;
|
||||
|
||||
try {
|
||||
const result = await apiFunction(...args);
|
||||
setData(result);
|
||||
|
||||
// Notifier que le serveur répond (pour une détection immédiate)
|
||||
// Cette ligne se déclenchera dès qu'un appel API réussit
|
||||
|
||||
if (showSuccessToast && toast.current) {
|
||||
toast.current.show({
|
||||
severity: 'success',
|
||||
summary: 'Succès',
|
||||
detail: successMessage,
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err: any) {
|
||||
setError(err);
|
||||
|
||||
// Déclenchement d'un health check urgent uniquement pour les erreurs critiques
|
||||
if (shouldRetry(err) && retryCountRef.current === 0) {
|
||||
// Health check urgent seulement au premier échec pour éviter le spam
|
||||
apiService.checkServerHealth(true).catch(() => {});
|
||||
}
|
||||
|
||||
// Gestion des erreurs réseau avec retry automatique
|
||||
if (shouldRetry(err) && retryCountRef.current < retryAttempts) {
|
||||
retryCountRef.current++;
|
||||
|
||||
if (showErrorToast && toast.current) {
|
||||
toast.current.show({
|
||||
severity: 'warn',
|
||||
summary: 'Tentative de reconnexion',
|
||||
detail: `Tentative ${retryCountRef.current}/${retryAttempts}...`,
|
||||
life: 2000
|
||||
});
|
||||
}
|
||||
|
||||
// Attendre avant de retry
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
return execute(...args);
|
||||
}
|
||||
|
||||
// Afficher l'erreur finale
|
||||
if (showErrorToast && toast.current) {
|
||||
const message = getErrorMessage(err);
|
||||
toast.current.show({
|
||||
severity: 'error',
|
||||
summary: 'Erreur',
|
||||
detail: message,
|
||||
life: err.statusCode === 'SERVER_UNAVAILABLE' ? 0 : 5000 // Sticky pour erreurs serveur
|
||||
});
|
||||
}
|
||||
|
||||
if (onError) {
|
||||
onError(err);
|
||||
}
|
||||
|
||||
return null;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [apiFunction, onSuccess, onError, showSuccessToast, showErrorToast, successMessage, retryAttempts, retryDelay]);
|
||||
|
||||
const retry = useCallback(async (): Promise<T | null> => {
|
||||
if (lastArgsRef.current.length > 0) {
|
||||
return execute(...lastArgsRef.current);
|
||||
}
|
||||
return execute();
|
||||
}, [execute]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setData(null);
|
||||
setError(null);
|
||||
setLoading(false);
|
||||
retryCountRef.current = 0;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
execute,
|
||||
retry,
|
||||
reset,
|
||||
toast
|
||||
};
|
||||
}
|
||||
|
||||
// Fonctions utilitaires
|
||||
function shouldRetry(error: any): boolean {
|
||||
return error?.statusCode === 'NETWORK_ERROR' ||
|
||||
error?.statusCode === 'SERVER_UNAVAILABLE' ||
|
||||
error?.statusCode === 'TIMEOUT' ||
|
||||
error?.code === 'ECONNABORTED' ||
|
||||
error?.code === 'ERR_NETWORK';
|
||||
}
|
||||
|
||||
function getErrorMessage(error: any): string {
|
||||
if (error?.statusCode === 'SERVER_UNAVAILABLE') {
|
||||
return 'Serveur indisponible. Vérifiez que le serveur backend est démarré.';
|
||||
}
|
||||
|
||||
if (error?.statusCode === 'NETWORK_ERROR') {
|
||||
return 'Erreur réseau. Vérifiez votre connexion internet.';
|
||||
}
|
||||
|
||||
if (error?.statusCode === 'TIMEOUT') {
|
||||
return 'Délai d\'attente dépassé. Le serveur met trop de temps à répondre.';
|
||||
}
|
||||
|
||||
return error?.userMessage || error?.message || 'Une erreur inattendue s\'est produite';
|
||||
}
|
||||
|
||||
export default useApiCall;
|
||||
Reference in New Issue
Block a user