ReactJS - Ajouter un écouteur d'événement personnalisé au composant

87

En javascript simple, j'ai le DIV

<div class="movie" id="my_movie">

et le code javascript suivant

var myMovie = document.getElementById('my_movie');
myMovie.addEventListener('nv-enter', function (event) {
     console.log('change scope');
});

Maintenant, j'ai un composant React, à l'intérieur de ce composant, dans la méthode de rendu, je retourne mon div. Comment puis-je ajouter un écouteur d'événement pour mon événement personnalisé? (J'utilise cette bibliothèque pour les applications TV - navigation )

import React, { Component } from 'react';

class MovieItem extends Component {

  render() {

    if(this.props.index === 0) {
      return (
        <div aria-nv-el aria-nv-el-current className="menu_item nv-default">
            <div className="indicator selected"></div>
            <div className="category">
                <span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
            </div>
        </div>
      );
    }
    else {
      return (
        <div aria-nv-el className="menu_item nv-default">
            <div className="indicator selected"></div>
            <div className="category">
                <span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
            </div>
        </div>
      );
    }
  }

}

export default MovieItem;

Mise à jour n ° 1:

entrez la description de l'image ici

J'ai appliqué toutes les idées fournies dans les réponses. J'ai mis la bibliothèque de navigation en mode débogage et je suis capable de naviguer sur mes éléments de menu uniquement en fonction du clavier (comme vous pouvez le voir sur la capture d'écran, j'ai pu naviguer vers Films 4) mais lorsque je concentre un élément dans le menu ou appuyez sur Entrée, je ne vois rien dans la console.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class MenuItem extends Component {

  constructor(props) {
    super(props);
    // Pre-bind your event handler, or define it as a fat arrow in ES7/TS
    this.handleNVFocus = this.handleNVFocus.bind(this);
    this.handleNVEnter = this.handleNVEnter.bind(this);
    this.handleNVRight = this.handleNVRight.bind(this);
  }

  handleNVFocus = event => {
      console.log('Focused: ' + this.props.menuItem.caption.toUpperCase());
  }

  handleNVEnter = event => {
      console.log('Enter: ' + this.props.menuItem.caption.toUpperCase());
  }

  handleNVRight = event => {
      console.log('Right: ' + this.props.menuItem.caption.toUpperCase());
  }

  componentDidMount() {
    ReactDOM.findDOMNode(this).addEventListener('nv-focus', this.handleNVFocus);
    ReactDOM.findDOMNode(this).addEventListener('nv-enter', this.handleNVEnter);
    ReactDOM.findDOMNode(this).addEventListener('nv-right', this.handleNVEnter);
    //this.refs.nv.addEventListener('nv-focus', this.handleNVFocus);
    //this.refs.nv.addEventListener('nv-enter', this.handleNVEnter);
    //this.refs.nv.addEventListener('nv-right', this.handleNVEnter);
  }

  componentWillUnmount() {
    ReactDOM.findDOMNode(this).removeEventListener('nv-focus', this.handleNVFocus);
    ReactDOM.findDOMNode(this).removeEventListener('nv-enter', this.handleNVEnter);
    ReactDOM.findDOMNode(this).removeEventListener('nv-right', this.handleNVRight);
    //this.refs.nv.removeEventListener('nv-focus', this.handleNVFocus);
    //this.refs.nv.removeEventListener('nv-enter', this.handleNVEnter);
    //this.refs.nv.removeEventListener('nv-right', this.handleNVEnter);
  }

  render() {
    var attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};
    return (
      <div ref="nv" aria-nv-el {...attrs} className="menu_item nv-default">
          <div className="indicator selected"></div>
          <div className="category">
              <span className="title">{this.props.menuItem.caption.toUpperCase()}</span>
          </div>
      </div>
    )
  }

}

export default MenuItem;

J'ai laissé quelques lignes commentées car dans les deux cas, je ne parviens pas à enregistrer les lignes de la console.

Mise à jour n ° 2: Cette bibliothèque de navigation ne fonctionne pas bien avec React avec ses balises Html d'origine, j'ai donc dû définir les options et renommer les balises pour utiliser aria- * afin que cela n'impacte pas React.

navigation.setOption('prefix','aria-nv-el');
navigation.setOption('attrScope','aria-nv-scope');
navigation.setOption('attrScopeFOV','aria-nv-scope-fov');
navigation.setOption('attrScopeCurrent','aria-nv-scope-current');
navigation.setOption('attrElement','aria-nv-el');
navigation.setOption('attrElementFOV','aria-nv-el-fov');
navigation.setOption('attrElementCurrent','aria-nv-el-current');
Thiago
la source
@Le J'utilise essentiellement l'exemple de ce fichier ( github.com/ahiipsa/navigation/blob/master/demo/index.html )
Thiago
Vous n'avez pas besoin à la fois de pré-lier dans votre constructeur ( this.handleNVEnter = this.handleNVEnter.bind(this)) et d'utiliser des initialiseurs de propriété ES7 avec des fonctions fléchées ( handleNVEnter = enter => {}) car les fonctions flèches grasses sont toujours liées. Si vous pouvez utiliser la syntaxe ES7, allez-y.
Aaron Beall
1
Merci Aaron. J'ai pu résoudre le problème. Je vais accepter votre réponse car j'utilise maintenant votre solution mais j'ai également dû faire autre chose. Étant donné que les balises HTML de la bibliothèque Nagivation ne fonctionnent pas bien avec React, j'ai dû définir les noms de balises dans la configuration de la lib pour utiliser le préfixe aria- *, le problème est que les événements ont également été déclenchés en utilisant le même préfixe, donc en définissant l'événement sur aria -nv-enter a fait l'affaire! Cela fonctionne bien maintenant. Merci!
Thiago
Je recommanderais de changer aria-*en data-*parce que les attributs ARIA proviennent d'un ensemble standard, vous ne pouvez pas créer les vôtres. Les attributs de données peuvent être définis plus arbitrairement sur ce que vous voulez.
Marcy Sutton

Réponses:

86

Si vous avez besoin de gérer des événements DOM qui ne sont pas déjà fournis par React, vous devez ajouter des écouteurs DOM une fois le composant monté:

Mise à jour: Entre React 13, 14 et 15, des modifications ont été apportées à l'API qui affectent ma réponse. Vous trouverez ci-dessous la dernière façon d'utiliser React 15 et ES7. Consultez l' historique des réponses pour les anciennes versions.

class MovieItem extends React.Component {

  componentDidMount() {
    // When the component is mounted, add your DOM listener to the "nv" elem.
    // (The "nv" elem is assigned in the render function.)
    this.nv.addEventListener("nv-enter", this.handleNvEnter);
  }

  componentWillUnmount() {
    // Make sure to remove the DOM listener when the component is unmounted.
    this.nv.removeEventListener("nv-enter", this.handleNvEnter);
  }

  // Use a class arrow function (ES7) for the handler. In ES6 you could bind()
  // a handler in the constructor.
  handleNvEnter = (event) => {
    console.log("Nv Enter:", event);
  }

  render() {
    // Here we render a single <div> and toggle the "aria-nv-el-current" attribute
    // using the attribute spread operator. This way only a single <div>
    // is ever mounted and we don't have to worry about adding/removing
    // a DOM listener every time the current index changes. The attrs 
    // are "spread" onto the <div> in the render function: {...attrs}
    const attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};

    // Finally, render the div using a "ref" callback which assigns the mounted 
    // elem to a class property "nv" used to add the DOM listener to.
    return (
      <div ref={elem => this.nv = elem} aria-nv-el {...attrs} className="menu_item nv-default">
        ...
      </div>
    );
  }

}

Exemple sur Codepen.io

Aaron Beall
la source
2
Vous abusez findDOMNode. Dans votre cas, cela var elem = this.refs.nv;suffit.
Pavlo
1
@Pavlo Hm, vous avez raison, il semble que cela ait changé dans la v.14 (pour renvoyer l'élément DOM au lieu de l'élément React comme dans la v.13, ce que j'utilise). Merci.
Aaron Beall
2
Pourquoi dois-je "Assurez-vous de supprimer l'écouteur DOM lorsque le composant est démonté"? Y a-t-il une source qui crée une fuite?
Fen1kz
1
@levininja Cliquez sur le edited Aug 19 at 6:19texte sous l'article, qui vous amène à l' historique des révisions .
Aaron Beall
1
@ NicolasS.Xu React ne fournit aucune API de distribution d'événements personnalisée car vous êtes censé utiliser des accessoires de rappel (voir cette réponse ), mais vous pouvez utiliser le DOM standard nv.dispatchEvent()si vous en avez besoin.
Aaron Beall
20

Vous pouvez utiliser les méthodes componentDidMount et componentWillUnmount :

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class MovieItem extends Component
{
    _handleNVEvent = event => {
        ...
    };

    componentDidMount() {
        ReactDOM.findDOMNode(this).addEventListener('nv-event', this._handleNVEvent);
    }

    componentWillUnmount() {
        ReactDOM.findDOMNode(this).removeEventListener('nv-event', this._handleNVEvent);
    }

    [...]

}

export default MovieItem;
vbarbarosh
la source
Salut @vbarbarosh, j'ai mis à jour la question avec plus de détails
Thiago
4

Tout d'abord, les événements personnalisés ne fonctionnent pas bien avec les composants React de manière native. Vous ne pouvez donc pas simplement le dire <div onMyCustomEvent={something}>dans la fonction de rendu et vous devez penser au problème.

Deuxièmement, après avoir jeté un coup d'œil à la documentation de la bibliothèque que vous utilisez, l'événement est en fait déclenché document.body, donc même s'il fonctionnait, votre gestionnaire d'événements ne se déclencherait jamais.

Au lieu de cela, componentDidMountquelque part dans votre application, vous pouvez écouter nv-enter en ajoutant

document.body.addEventListener('nv-enter', function (event) {
    // logic
});

Ensuite, à l'intérieur de la fonction de rappel, appuyez sur une fonction qui change l'état du composant, ou tout ce que vous voulez faire.

dannyjolie
la source
2
Pourriez-vous aider à expliquer pourquoi «les événements personnalisés ne fonctionnent pas bien avec les composants React de manière native»?
codeful.element
1
@ codeful.element, ce site contient des informations à ce sujet: custom-elements-everywhere.com/#react
Paul-Hebert