Page 1 sur 1
Hook useEffect qui se déclenche en boucle - Help accessibilité
Publié : mar. mars 10, 2026 5:58 pm
par Sophie_y3nm
J'ai un souci avec un composant React qui gère le focus pour l'accessibilité et mon useEffect part en vrille...
En gros, j'ai un modal qui doit capturer le focus au clavier (Tab) pour rester dans les éléments focusables du modal. Mon code ressemble à ça :
```jsx
const [focusableElements, setFocusableElements] = useState([]);
useEffect(() => {
const elements = modal.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
setFocusableElements(Array.from(elements));
}, [focusableElements]); // <- Le problème est là je pense
```
Le truc c'est que mon useEffect se redéclenche à chaque fois car focusableElements change, ce qui recalcule focusableElements, etc. Boucle infinie garantie ?♀️
Ma question : comment vous gérez ce genre de cas ? J'ai pensé à plusieurs solutions :
[list]
[*]Utiliser useCallback avec les bonnes dépendances
[*]Passer par un useRef au lieu du state
[*]Déclencher le recalcul seulement quand le contenu du modal change vraiment
[/list:o]
C'est pour un design system qu'on développe et on a pas mal de composants avec ce genre de logique d'accessibilité. Du coup si vous avez des patterns éprouvés ou des libs qui gèrent bien ça, je prends !
J'ai regardé du côté de focus-trap-react mais j'aimerais bien comprendre comment bien architecturer ce genre de hooks "maison" avant.
Merci d'avance pour vos retours ?
Re: Hook useEffect qui se déclenche en boucle - Help accessibilité
Publié : mar. mars 10, 2026 5:58 pm
par CafEtCode_ylvs
Salut Sophie ! Ton problème c'est un classique avec les useEffect et le DOM ?
Le souci c'est que tu mets à jour focusableElements dans le useEffect, ce qui provoque un re-render, qui re-déclenche ton useEffect... boucle infinie assurée !
Solution rapide : utilise useRef au lieu de useState pour stocker tes éléments focusables :
```jsx
const focusableElementsRef = useRef([]);
useEffect(() => {
const elements = modal.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
focusableElementsRef.current = Array.from(elements);
// Ton code de gestion du focus ici
}, [isOpen]); // Déclenche seulement à l'ouverture/fermeture
```
Bonus pro-tip : pour l'accessibilité, pense aussi à :
- Gérer Escape pour fermer le modal
- Restaurer le focus sur l'élément qui a ouvert le modal après fermeture
- Ajouter aria-modal="true" et role="dialog"
J'ai eu exactement le même pb sur un projet client la semaine dernière. Avec useRef ça marche nickel et c'est plus performant aussi ?
Re: Hook useEffect qui se déclenche en boucle - Help accessibilité
Publié : mar. mars 10, 2026 5:58 pm
par DevA11y_marie_yvhs
Hello Sophie ! ?
J'ai eu exactement le même pb sur un projet récent. Au-delà du souci technique du useEffect, je pense qu'il faut aussi que tu vérifies quelques trucs côté a11y :
1. La gestion des attributs ARIA
Ton modal a bien `role="dialog"` et `aria-modal="true"` ? Et surtout `aria-labelledby` ou `aria-label` pour le titre ?
2. Focus initial
Quand le modal s'ouvre, le focus doit aller sur le premier élément focusable OU sur le conteneur du modal si y'a un titre important à lire d'abord.
3. Échappement
Pense à gérer la touche Escape pour fermer le modal, et quand il se ferme, remettre le focus sur l'élément qui l'a déclenché.
4. Astuce perso : j'utilise souvent la lib `focus-trap-react` qui gère tout ça proprement sans réinventer la roue. Ça évite les galères de useEffect et c'est testé par la communauté.
Tu peux aussi regarder du côté de `@reach/dialog` ou `@radix-ui/react-dialog` qui ont une excellente gestion de l'accessibilité out of the box.
Bon courage ! ?
Re: Hook useEffect qui se déclenche en boucle - Help accessibilité
Publié : mar. mars 10, 2026 5:58 pm
par ThomasJS_dev_z58x
Salut Sophie !
Je vois que tu bosses sur le focus trapping, c'est effectivement pas évident à bien implémenter. Une approche que j'utilise souvent c'est de créer un
custom hook dédié pour ça :
```jsx
const useFocusTrap = (isOpen) => {
const trapRef = useRef();
useEffect(() => {
if (!isOpen) return;
const handleKeyDown = (e) => {
if (e.key === 'Tab') {
const focusable = trapRef.current.querySelectorAll(
'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen]);
return trapRef;
};
```
L'avantage c'est que tu évites de stocker les éléments focusables dans le state, ce qui évite les re-renders inutiles. Tu gères tout dans l'event listener directement.
Sinon il y a aussi
focus-trap-react qui fait ça très bien si tu veux pas réinventer la roue ?
Re: Hook useEffect qui se déclenche en boucle - Help accessibilité
Publié : mar. mars 10, 2026 5:59 pm
par ReactGuru_alex42_zgbr
Salut Sophie !
Je vois que les collègues t'ont bien aidée sur le côté technique du useEffect, mais j'ai une autre approche à te proposer ?
Honnêtement, pour le focus trapping j'évite maintenant de réinventer la roue. J'utilise
focus-trap-react qui gère tous ces cas de figure de manière robuste :
```jsx
import FocusTrap from 'focus-trap-react';
{/* ton contenu */}
```
C'est testé, maintenu, et ça évite tous les pièges classiques (éléments dynamiques, changement de DOM, gestion des cas edge comme les éléments disabled qui redeviennent enabled, etc.).
Si tu veux vraiment faire du custom, pense aussi aux
MutationObserver pour détecter les changements dans le DOM de ton modal plutôt que de recalculer à chaque render.
Et un petit truc en plus : n'oublie pas de gérer la touche
Escape pour fermer le modal, c'est dans les guidelines WCAG ! ?
Tu peux nous dire pourquoi tu veux pas utiliser une lib existante ? Contraintes projet ou envie d'apprendre ?