J'ai un composant parent qui peut obtenir le focus. Il utilise ce focus pour offrir des commandes au clavier. Ce composant parent peut engendrer un composant enfant qui de la même manière peut prendre le focus afin de pouvoir répondre aux événements de clavier. L'un des événements de clavier que le composant enfant écoute est , ce qui provoque le démontage du composant enfant.
Lorsque le composant enfant est démonté, le focus est renvoyé sur le du document.
Comment mon composant parent peut-il détecter lorsque cela se produit et se réaffecter le focus?
Ce que je sais jusqu'à présent:
onBlur synthétique de onFocusOut , mais si j'en enregistre un directement en utilisant une ref, j'obtiens un événement m'indiquant que l'enfant a démonté. Cependant, je ne peux pas faire la distinction entre un événement focusout déclenché par un enfant démontant et un événement focusout déclenché par un utilisateur cliquant sur une autre cible de clic. Modifier : je recherche une solution qui n'implique pas de communication / couplage direct entre les composants parent et enfant. Imaginez que je puisse arbitrairement beaucoup d'enfants comme celui-ci dans un arbre arbitrairement profondément imbriqué.
3 Réponses :
Le composant enfant doit accepter un accessoire de fonction qui est appelé lorsque esc est pressé (ou partout ailleurs où il doit être appelé).
// Render function of Parent
const functionThatGetsCalledWhenEscIsPressed = function(){
this.input.focus();
}
return (
<div>
<Child onDestroy={functionThatGetsCalledWhenEscIsPressed} />
<input ref={(input) => this.input = input} />
</div>
);
J'ai modifié ma question pour préciser que je recherche une solution qui n'implique pas de couplage étroit entre le parent et l'enfant.
Vous pouvez passer un rappel du parent à l'enfant à appeler dans le cas où la touche est enfoncée (ou simplement dans la méthode componentWillUnmount de l'enfant). Ce rappel peut simplement transférer le focus vers le parent, de la même manière que d'habitude.
Par exemple:
class Parent extends React.Component {
focusSelf() {
// do stuff to focus self
}
render() {
return (
<div>
<Child beforeUnmount={ this.focusSelf }/>
</div>
)
}
}
class Child extends React.Component {
componentWillUnmount() {
const { beforeUnmount } = this.props;
beforeUnmount();
}
}
J'ai modifié ma question pour préciser que je recherche une solution qui n'implique pas de couplage étroit entre le parent et l'enfant.
@JordanEldredge hmm, vous pourrez peut-être le faire en utilisant une solution d'état globale avec l'API de contexte ou Redux. Mais voulez-vous simplement éviter le couplage étroit entre Parent et Enfant juste pour gérer une profondeur arbitraire? ou y a-t-il d'autres raisons?
La raison pour laquelle je veux éviter le couplage est simplement architecturale. Il ne semble pas viable de demander à tous les ingénieurs (ou à moi le mois prochain) de ne pas oublier d'opter pour ce système chaque fois qu'ils ajoutent un élément focalisable.
J'ai fini par résoudre ce problème en utilisant un MutationObserver.
Le code ressemble à ceci:
// It's possible for a child component to gain focus and then become
// unmounted. In that case, the browser will return focus to the `<body>`.
// In the following hook, use a `MutationObserver` to watch for that behavior
// and refocus the containing FocusTarget when it happens.
//
// I tried a number of other approaches using `focus/blur/focusin/focusout` on
// various DOM nodes, and was unable to find a solution which would trigger in
// this senario in Firefox. Therefore we use this `MutationObserver` approach.
useEffect(() => {
// Only create the `MutationObserver` within the currently focused target.
if (ref == null || windowId !== focusedWindowId) {
return;
}
const observer = new MutationObserver(mutations => {
// In the common case we won't have focused the body, so we can do this
// inexpensive check first to avoid calling the more expensive `O(n)`
// check of the individual mutations.
if (document.activeElement !== document.body) {
return;
}
if (mutations.some(mutation => mutation.removedNodes.length > 0)) {
ref.focus();
}
});
observer.observe(ref, {
subtree: true,
attributes: false,
childList: true
});
return () => observer.disconnect();
}, [windowId, focusedWindowId, ref]);
Commit réel en l'ajoutant: https://github.com/captbaritone/webamp/commit/2dca07ff0a97ad378a1bp>