Quelle est la meilleure façon de déclencher un événement onchange dans react js


112

Nous utilisons le bundle Backbone + ReactJS pour créer une application côté client. En s'appuyant fortement sur des notoires, valueLinknous propagons les valeurs directement au modèle via notre propre wrapper qui prend en charge l'interface ReactJS pour la liaison bidirectionnelle.

Maintenant, nous avons fait face au problème:

Nous avons un jquery.mask.jsplugin qui formate la valeur d'entrée par programme afin qu'il ne déclenche pas les événements React. Tout cela conduit à une situation où le modèle reçoit des valeurs non formatées de l'entrée de l'utilisateur et manque celles formatées du plugin.

Il semble que React dispose de nombreuses stratégies de gestion des événements en fonction du navigateur. Existe-t-il un moyen courant de déclencher un événement de changement pour un élément DOM particulier afin que React l'entende?


Réponses:


175

Pour React 16 et React> = 15,6

Setter .value=ne fonctionne pas comme nous le voulions car la bibliothèque React remplace le setter de valeur d'entrée, mais nous pouvons appeler la fonction directement sur le inputcontexte as.

var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, 'react 16 value');

var ev2 = new Event('input', { bubbles: true});
input.dispatchEvent(ev2);

Pour un élément textarea vous devez utiliser prototypede HTMLTextAreaElementclasse.

Nouvel exemple de codepen .

Tous les crédits à ce contributeur et à sa solution

Réponse obsolète uniquement pour React <= 15,5

Avec, react-dom ^15.6.0vous pouvez utiliser un simulatedindicateur sur l'objet événement pour que l'événement passe

var ev = new Event('input', { bubbles: true});
ev.simulated = true;
element.value = 'Something new';
element.dispatchEvent(ev);

J'ai fait un codepen avec un exemple

Pour comprendre pourquoi un nouveau drapeau est nécessaire, j'ai trouvé ce commentaire très utile:

La logique d'entrée dans React déduplique désormais les événements de modification de sorte qu'ils ne se déclenchent pas plus d'une fois par valeur. Il écoute à la fois les événements onChange / onInput du navigateur ainsi que les ensembles sur le prop de valeur du nœud DOM (lorsque vous mettez à jour la valeur via javascript). Cela a pour effet secondaire de signifier que si vous mettez à jour la valeur de l'entrée manuellement input.value = 'foo', puis envoyez un ChangeEvent avec {target: input} React enregistrera à la fois l'ensemble et l'événement, voir que sa valeur est toujours `` 'foo ', considérez-le comme un événement en double et avalez-le.

Cela fonctionne bien dans les cas normaux car un "vrai" événement lancé par le navigateur ne déclenche pas de jeux sur l'élément element.value. Vous pouvez sortir secrètement de cette logique en étiquetant l'événement que vous déclenchez avec un drapeau simulé et réagir déclenchera toujours l'événement. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128


1
Merci, cela a fonctionné à partir de react-dom ^ 15.6.0. Mais il semble que depuis React 16.0, cela ne fonctionne plus. Une idée sur l'alternative à l'utilisation d'un indicateur simulé pour déclencher les événements de changement?
Qwerty

Croyez qu'il y a un point ici qui donnerait un indice: reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Une idée de ce que cela pourrait être?
Qwerty

@Qwerty J'ai mis à jour ma réponse, peut-être que cela fonctionnera pour vous
Grin

et quel bouton onClick? que faut-il faire pour déclencher ce genre d'événement?
Bender

@Bender vous venez d'appeler la méthode de clic native sur l'élément
Grin

63

Au moins sur les entrées de texte, il semble onChangeêtre à l'écoute des événements d'entrée:

var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);

Dépend de la version du navigateur. IE8 ne prend pas en charge l'événement d'entrée. Et ie9 ne déclenche pas d'événement d'entrée lorsque vous supprimez des caractères de la zone de texte. developer.mozilla.org/en-US/docs/Web/Events/input
wallice


1
IE8 n'est plus pris en charge par React. Pour IE9, vous pourrez peut-être utiliser quelque chose comme var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, mais je n'ai pas de machine virtuelle IE9 à portée de main.
Michael

@Michael J'essaye votre code sur IE11 et cela ne fonctionne pas sur les champs de saisie reactjs. Cela fonctionne sur les entrées HTML normales. Cela fonctionne également sur Edge. var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('input', true, false, { }); document.getElementById('description').value = 'I changed description'; document.getElementById('description').dispatchEvent(evt);
Bodosko

19

Je sais que cette réponse arrive un peu tard mais j'ai récemment été confronté à un problème similaire. Je voulais déclencher un événement sur un composant imbriqué. J'avais une liste avec des widgets de type radio et case à cocher (c'étaient des div qui se comportaient comme des cases à cocher et / ou des boutons radio) et à un autre endroit de l'application, si quelqu'un fermait une boîte à outils, je devais en décocher une.

J'ai trouvé une solution assez simple, je ne sais pas si c'est la meilleure pratique, mais cela fonctionne.

var event = new MouseEvent('click', {
 'view': window, 
 'bubbles': true, 
 'cancelable': false
});
var node = document.getElementById('nodeMyComponentsEventIsConnectedTo');
node.dispatchEvent(event);

Cela a déclenché l'événement de clic sur le domNode et mon gestionnaire attaché via react a en effet été appelé, il se comporte comme je m'y attendais si quelqu'un cliquait sur l'élément. Je n'ai pas testé onChange mais cela devrait fonctionner, et je ne sais pas comment cela fonctionnera dans les versions vraiment anciennes d'IE, mais je crois que MouseEvent est pris en charge dans au moins IE9 et plus.

Je me suis finalement éloigné de cela pour mon cas d'utilisation particulier car mon composant était très petit (seule une partie de mon application utilisée réagit puisque je suis toujours en train de l'apprendre) et je pourrais réaliser la même chose d'une autre manière sans obtenir de références aux nœuds dom.

METTRE À JOUR:

Comme d'autres l'ont indiqué dans les commentaires, il est préférable d'utiliser this.refs.refnamepour obtenir une référence à un nœud dom. Dans ce cas, refname est la référence que vous avez attachée à votre composant via <MyComponent ref='refname' />.


1
Au lieu d'ID, vous pouvez utiliser la React.findDOMNodefonction. goo.gl/RqccrA
m93a

2
> Au lieu de l'ID, vous pouvez utiliser la fonction React.findDOMNode. Ou ajoutez un refà votre élément puis utilisezthis.ref.refName.dispatchEvent
silkAdmin

Le cadre a probablement changé depuis, c'est this.ref s .refname
nclord

1
Les références de chaîne sont désormais considérées comme héritées et seront obsolètes à l'avenir. Les références de rappel sont la méthode préférée pour garder une trace du nœud DOM.
dzv3

9

Vous pouvez simuler des événements à l'aide de ReactTestUtils, mais c'est conçu pour les tests unitaires.

Je recommanderais de ne pas utiliser valueLink pour ce cas et d'écouter simplement les événements de changement déclenchés par le plugin et de mettre à jour l'état de l'entrée en réponse. Les utilitaires de liaison bidirectionnelle sont plus une démonstration qu'autre chose; ils sont inclus dans des modules complémentaires uniquement pour souligner le fait que la liaison bidirectionnelle pure n'est pas appropriée pour la plupart des applications et que vous avez généralement besoin de plus de logique d'application pour décrire les interactions dans votre application.


J'avais peur que la réponse soit quelque chose comme ça (. Le problème est que React est trop strict avec le flux de travail d'envoi / réception. En particulier, il faut spécifier le gestionnaire onChange pour pouvoir réagir à l'entrée de l'utilisateur, pas d'autre moyen que l'état incontrôlé qui est gentil Dans mon cas, je devrais déclarer ce gestionnaire uniquement pour suivre les règles, tandis que les entrées pertinentes proviendront de l'événement onchange déclenché par jquery. React n'a vraiment pas l'idée d'étendre les points de terminaison d'envoi / réception avec le code déclaré par l'utilisateur
wallice

1
Et ... au cas où vous voudriez utiliser ReactTestUtils ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin

8

S'étendant sur la réponse de Grin / Dan Abramov, cela fonctionne sur plusieurs types d'entrée. Testé dans React> = 15,5

const inputTypes = [
    window.HTMLInputElement,
    window.HTMLSelectElement,
    window.HTMLTextAreaElement,
];

export const triggerInputChange = (node, value = '') => {

    // only process the change on elements we know have a value setter in their constructor
    if ( inputTypes.indexOf(node.__proto__.constructor) >-1 ) {

        const setValue = Object.getOwnPropertyDescriptor(node.__proto__, 'value').set;
        const event = new Event('input', { bubbles: true });

        setValue.call(node, value);
        node.dispatchEvent(event);

    }

};

5
Ne fonctionne pas pour certains éléments. Vous avez besoin de «changement» au lieu de «entrée» pour l'événement.
styks

3

Le déclenchement d'événements de changement sur des éléments arbitraires crée des dépendances entre les composants sur lesquelles il est difficile de raisonner. Il est préférable de s'en tenir au flux de données à sens unique de React.

Il n'y a pas d'extrait de code simple pour déclencher l'événement de modification de React. La logique est implémentée dans ChangeEventPlugin.js et il existe différentes branches de code pour différents types d'entrée et navigateurs. De plus, les détails de mise en œuvre varient selon les versions de React.

J'ai construit react-trigger-change qui fait la chose, mais il est destiné à être utilisé pour des tests, pas comme une dépendance de production:

let node;
ReactDOM.render(
  <input
    onChange={() => console.log('changed')}
    ref={(input) => { node = input; }}
  />,
  mountNode
);

reactTriggerChange(node); // 'changed' is logged

CodePen


1

puisque nous utilisons des fonctions pour gérer un événement onchange, nous pouvons le faire comme ceci:

class Form extends Component {
 constructor(props) {
  super(props);
  this.handlePasswordChange = this.handlePasswordChange.bind(this);
  this.state = { password: '' }
 }

 aForceChange() {
  // something happened and a passwordChange
  // needs to be triggered!!

  // simple, just call the onChange handler
  this.handlePasswordChange('my password');
 }

 handlePasswordChange(value) {
 // do something
 }

 render() {
  return (
   <input type="text" value={this.state.password} onChange={changeEvent => this.handlePasswordChange(changeEvent.target.value)} />
  );
 }
}

1

J'ai trouvé ceci sur les problèmes Github de React: Fonctionne comme un charme (v15.6.2)

Voici comment j'ai implémenté une entrée de texte:

changeInputValue = newValue => {

    const e = new Event('input', { bubbles: true })
    const input = document.querySelector('input[name=' + this.props.name + ']')
    console.log('input', input)
    this.setNativeValue(input, newValue)
    input.dispatchEvent(e)
  }

  setNativeValue (element, value) {
    const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set
    const prototype = Object.getPrototypeOf(element)
    const prototypeValueSetter = Object.getOwnPropertyDescriptor(
      prototype,
      'value'
    ).set

    if (valueSetter && valueSetter !== prototypeValueSetter) {
      prototypeValueSetter.call(element, value)
    } else {
      valueSetter.call(element, value)
    }
  }

Cela ne fonctionne pas si la valeur de la zone de texte est la même que celle que vous transmettez à setNativeValue
Arun

0

Pour HTMLSelectElement, ie<select>

var element = document.getElementById("element-id");
var trigger = Object.getOwnPropertyDescriptor(
  window.HTMLSelectElement.prototype,
  "value"
).set;
trigger.call(element, 4); // 4 is the select option's value we want to set
var event = new Event("change", { bubbles: true });
element.dispatchEvent(event);

0

Le type d'événement inputn'a pas fonctionné pour moi, <select>mais le changer en changefonctionne

useEffect(() => {
    var event = new Event('change', { bubbles: true });
    selectRef.current.dispatchEvent(event); // ref to the select control
}, [props.items]);

-1

Si vous utilisez Backbone et React, je vous recommande l'un des éléments suivants,

Ils aident tous les deux à intégrer les modèles et les collections Backbone aux vues React. Vous pouvez utiliser les événements Backbone comme vous le faites avec les vues Backbone. Je l' ai touché à la fois et n'a pas vu beaucoup de différence , sauf un est un mixin et les autres changements React.createClassà React.createBackboneClass.


Soyez prudent avec ces ponts "événementiels" entre react et backbone. Le premier plugin utilise setProps sans tenir compte du niveau de montage des composants. C'est une erreur. Second repose fortement sur forceUpdate et pense qu'un seul modèle peut être attribué à un composant, ce qui n'est pas une situation courante. De plus, si vous partagez un modèle sur une interface utilisateur complexe avec des composants de réaction et que vous avez oublié de vous désabonner des événements de mise à jour inutiles qui provoquent forceUpdate, vous pouvez tomber dans le rendu récursif, soyez conscient de cela.
wallice
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.