Implementation de la notion d'espace d'investissement
This commit is contained in:
1 parent
6b6cad7cd6
commit
4b7df66862
54 files changed
+6782
-288
No files matched your search
@@ -0,0 +1,50 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
/**
|
||||
* useAsyncAction — garde anti-double-exécution pour un handler asynchrone
|
||||
* (soumission de formulaire, suppression, action d'un bouton…).
|
||||
*
|
||||
* Contexte : sur une connexion lente vers le serveur (VPN, 4G…), rien
|
||||
* n'indiquait visuellement qu'une requête était en cours. L'utilisateur,
|
||||
* ne voyant pas de retour, cliquait une seconde fois sur "Créer"/"Enregistrer"
|
||||
* avant la fermeture de la modale (qui n'intervient qu'après le succès de la
|
||||
* requête) — ce qui déclenchait un second POST et doublait la saisie en base.
|
||||
*
|
||||
* `run` protège contre ce phénomène à deux niveaux :
|
||||
* 1. Une ref (donc synchrone, pas soumise au re-render React) bloque tout
|
||||
* appel concurrent tant qu'un premier est en cours — même un double-clic
|
||||
* très rapproché, avant que le bouton n'ait eu le temps de se désactiver
|
||||
* visuellement, est ignoré.
|
||||
* 2. `pending` (état React) permet en plus de désactiver l'UI (bouton,
|
||||
* fermeture de la modale) et d'afficher un indicateur de chargement,
|
||||
* pour que l'utilisateur sache que sa requête est bien partie.
|
||||
*
|
||||
* Usage :
|
||||
* const { run, pending } = useAsyncAction();
|
||||
* ...
|
||||
* <button disabled={pending} onClick={() => run(submit)}>
|
||||
* {pending ? 'Enregistrement…' : 'Enregistrer'}
|
||||
* </button>
|
||||
*
|
||||
* `run` transmet ses arguments au handler (utile pour un onSubmit de <form>
|
||||
* qui reçoit l'événement) et retourne sa valeur de résolution. Un appel
|
||||
* ignoré (déjà en cours) résout silencieusement sur `undefined`.
|
||||
*/
|
||||
export function useAsyncAction() {
|
||||
const runningRef = useRef(false);
|
||||
const [pending, setPending] = useState(false);
|
||||
|
||||
const run = useCallback(async (handler, ...args) => {
|
||||
if (runningRef.current) return undefined;
|
||||
runningRef.current = true;
|
||||
setPending(true);
|
||||
try {
|
||||
return await handler(...args);
|
||||
} finally {
|
||||
runningRef.current = false;
|
||||
setPending(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { run, pending };
|
||||
}
|
||||
Reference in new issue
Block a user