Qual è il modo migliore per attivare l'evento onchange in react js


112

Usiamo il bundle Backbone + ReactJS per creare un'app lato client. Facendo molto affidamento sul famigerato valueLink, propaghiamo i valori direttamente al modello tramite il proprio wrapper che supporta l'interfaccia ReactJS per l'associazione a due vie.

Ora abbiamo affrontato il problema:

Abbiamo un jquery.mask.jsplugin che formatta il valore di input in modo programmatico, quindi non attiva gli eventi React. Tutto ciò porta a situazioni in cui il modello riceve valori non formattati dall'input dell'utente e manca quelli formattati dal plug-in.

Sembra che React abbia molte strategie di gestione degli eventi a seconda del browser. Esiste un modo comune per attivare l'evento di modifica per un particolare elemento DOM in modo che React lo ascolti?


👉 Discussione su React Github: attivazione della modifica del valore di input simulato per React 16
vsync

Risposte:


174

Per React 16 e React> = 15.6

Setter .value=non funziona come volevamo perché la libreria React sovrascrive il setter del valore di input ma possiamo chiamare la funzione direttamente nel inputcontesto 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);

Per elemento textarea si dovrebbe usare prototypedi HTMLTextAreaElementclasse.

Nuovo esempio di codepen .

Tutti i crediti a questo collaboratore e alla sua soluzione

Risposta obsoleta solo per React <= 15.5

Con react-dom ^15.6.0puoi usare il simulatedflag sull'oggetto evento per far passare l'evento

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

Ho fatto una codepen con un esempio

Per capire perché è necessaria una nuova bandiera ho trovato molto utile questo commento :

La logica di input in React ora deduplica gli eventi di modifica in modo che non vengano attivati ​​più di una volta per valore. Ascolta sia gli eventi onChange / onInput del browser che i set sul valore del nodo DOM (quando aggiorni il valore tramite javascript). Questo ha l'effetto collaterale di significare che se aggiorni il valore dell'input manualmente input.value = 'foo' quindi invia un ChangeEvent con {target: input} React registrerà sia il set che l'evento, vedrai che il suo valore è ancora `` foo ', consideralo un evento duplicato e inghiottilo.

Questo funziona bene in casi normali perché un evento avviato dal browser "reale" non attiva i set su element.value. Puoi salvare segretamente questa logica contrassegnando l'evento che attivi con una bandiera simulata e reagire attiverà sempre l'evento. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128


1
Grazie, ha funzionato da react-dom ^ 15.6.0. Ma sembra che dopo React 16.0 questo abbia smesso di funzionare. Qualche idea sull'alternativa all'uso di flag simulati per attivare gli eventi di modifica?
Qwerty

Credo che ci sia un punto qui che darebbe il suggerimento: reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Qualche idea di cosa potrebbe essere?
Qwerty

@Qwerty Ho aggiornato la mia risposta, forse funzionerà per te
Grin

e qual è il pulsante su clic? cosa si potrebbe fare per innescare questo tipo di evento?
Bender

@Bender, chiami semplicemente il metodo di clic nativo sull'elemento
Grin

63

Almeno sugli input di testo, sembra che onChangestia ascoltando gli eventi di input:

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

Dipende dalla versione del browser. IE8 non supporta l'evento di input. E ie9 non attiva l'evento di input quando rimuovi i caratteri dalla casella di testo. developer.mozilla.org/en-US/docs/Web/Events/input
wallice


1
IE8 non è più supportato da React. Per IE9, potresti essere in grado di usare qualcosa di simile var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, ma non ho una VM IE9 a portata di mano.
Michael

@ Michael sto provando il tuo codice su IE11 e non funziona sui campi di input di reactjs. Funziona su normali input HTML. Funziona anche su 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

So che questa risposta arriva un po 'tardi, ma recentemente ho affrontato un problema simile. Volevo attivare un evento su un componente nidificato. Avevo un elenco con widget di tipo radio e casella di controllo (erano div che si comportavano come caselle di controllo e / o pulsanti di opzione) e in qualche altro punto dell'applicazione, se qualcuno chiudeva una casella degli strumenti, dovevo deselezionarne una.

Ho trovato una soluzione piuttosto semplice, non sono sicuro che questa sia la migliore pratica ma funziona.

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

Ciò ha attivato l'evento click sul domNode e il mio gestore collegato tramite react è stato effettivamente chiamato, quindi si comporta come mi sarei aspettato se qualcuno avesse cliccato sull'elemento. Non ho testato onChange ma dovrebbe funzionare, e non sono sicuro di come andrà bene nelle versioni molto vecchie di IE, ma credo che MouseEvent sia supportato almeno in IE9 e versioni successive.

Alla fine mi sono allontanato da questo per il mio caso d'uso particolare perché il mio componente era molto piccolo (solo una parte della mia applicazione utilizzata reagisce poiché lo sto ancora imparando) e potevo ottenere la stessa cosa in un altro modo senza ottenere riferimenti ai nodi dom.

AGGIORNARE:

Come altri hanno affermato nei commenti, è meglio usare this.refs.refnameper ottenere un riferimento a un nodo dom. In questo caso, refname è il ref che hai collegato al tuo componente tramite <MyComponent ref='refname' />.


1
Invece di ID, puoi usare la React.findDOMNodefunzione. goo.gl/RqccrA
m93a

2
> Invece di ID, puoi usare la funzione React.findDOMNode. Oppure aggiungi una refal tuo elemento, quindi usathis.ref.refName.dispatchEvent
silkAdmin

Il framework è molto probabilmente cambiato da allora, è this.ref s .refname
nclord

1
I riferimenti di stringa sono considerati legacy ora e saranno obsoleti in futuro. I riferimenti di richiamata sono il metodo preferito per tenere traccia del nodo DOM.
dzv3

9

Puoi simulare eventi usando ReactTestUtils, ma è progettato per i test di unità.

Consiglierei di non utilizzare valueLink per questo caso e semplicemente di ascoltare gli eventi di modifica attivati ​​dal plug-in e aggiornare lo stato dell'input in risposta. La rilegatura a due vie è più una demo che altro; sono inclusi nei componenti aggiuntivi solo per sottolineare il fatto che l'associazione a due vie pura non è appropriata per la maggior parte delle applicazioni e che di solito è necessaria più logica dell'applicazione per descrivere le interazioni nella tua app.


Avevo paura che la risposta fosse qualcosa del genere (. Il problema è che React è troppo severo con il flusso di lavoro di invio / ricezione. In particolare è necessario specificare il gestore onChange per poter reagire all'input dell'utente, in nessun altro modo eccetto lo stato incontrollato che è gentile di over boilerplate per me.Nel mio caso dovrei dichiarare questo gestore solo per seguire le regole, mentre l'input rilevante verrà dall'evento onchange attivato da jquery. React manca davvero dell'idea di estendere gli endpoint di invio / ricezione con codice dichiarato dall'utente
wallice

1
E ... nel caso in cui desideri utilizzare ReactTestUtils ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin

8

Espandendo la risposta di Grin / Dan Abramov, questo funziona su più tipi di input. Testato in 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
Non funziona per gli elementi selezionati. È necessario "cambiare" invece di "input" per l'evento.
styks

3

L'attivazione di eventi di modifica su elementi arbitrari crea dipendenze tra i componenti su cui è difficile ragionare. È meglio attenersi al flusso di dati unidirezionale di React.

Non esiste un semplice snippet per attivare l'evento di modifica di React. La logica è implementata in ChangeEventPlugin.js e ci sono diversi rami di codice per diversi tipi di input e browser. Inoltre, i dettagli di implementazione variano tra le versioni di React.

Ho creato il cambio di attivazione della reazione che fa la cosa, ma è inteso per essere utilizzato per i test, non come dipendenza dalla produzione:

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

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

CodePen


1

bene poiché usiamo le funzioni per gestire un evento onchange, possiamo farlo in questo modo:

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

L'ho trovato sui problemi Github di React: funziona come un fascino (v15.6.2)

Ecco come ho implementato un input di testo:

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)
    }
  }

Questo non funziona se il valore della casella di testo è lo stesso del valore che stai passando a setNativeValue
Arun

0

Per 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);

-1

Se utilizzi Backbone e React, ti consiglio uno dei seguenti,

Entrambi aiutano a integrare i modelli e le raccolte Backbone con le viste React. Puoi utilizzare gli eventi Backbone proprio come fai con le viste Backbone. Ho sguazzato in entrambi e non ho visto molta differenza, tranne uno è un mixin e le altre modifiche React.createClassa React.createBackboneClass.


Per favore, fai attenzione a quei ponti "guidati dagli eventi" tra react e backbone Il primo plugin usa setProps senza tener conto del livello di montaggio dei componenti. È un errore. Il secondo si basa fortemente su forceUpdate e pensa che un solo modello potrebbe essere assegnato a un componente, il che non è una situazione comune. Inoltre, se condividi il modello su un'interfaccia utente complessa con componenti di reazione e ti sei dimenticato di annullare l'iscrizione a eventi di aggiornamento inutili che causano forceUpdate, allora puoi cadere nel rendering ricorsivo, tieni presente questo.
Wallice
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.