Apa cara terbaik untuk memicu acara onchange di react js

112

Kami menggunakan bundel Backbone + ReactJS untuk membangun aplikasi sisi klien. Sangat bergantung pada terkenal, valueLinkkami menyebarkan nilai langsung ke model melalui pembungkusnya sendiri yang mendukung antarmuka ReactJS untuk pengikatan dua arah.

Sekarang kami menghadapi masalah:

Kami memiliki jquery.mask.jsplugin yang memformat nilai input secara terprogram sehingga tidak mengaktifkan peristiwa React. Semua ini mengarah pada situasi ketika model menerima nilai yang tidak diformat dari input pengguna dan melewatkan yang diformat dari plugin.

Tampaknya React memiliki banyak strategi penanganan acara bergantung pada browser. Apakah ada cara umum untuk memicu peristiwa perubahan untuk elemen DOM tertentu sehingga React akan mendengarnya?

Wallice
sumber
👉 Diskusi React Github: Memicu simulasi perubahan nilai input untuk React 16
vsync

Jawaban:

174

Untuk React 16 dan React> = 15.6

Setter .value=tidak bekerja seperti yang kita inginkan karena perpustakaan React menimpa setter nilai input tetapi kita dapat memanggil fungsi tersebut secara langsung pada inputkonteks 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);

Untuk elemen textarea Anda harus menggunakan prototypedari HTMLTextAreaElementkelas.

Contoh codepen baru .

Semua kredit untuk kontributor ini dan solusinya

Jawaban usang hanya untuk React <= 15.5

Dengan react-dom ^15.6.0Anda dapat menggunakan simulatedbendera pada objek acara untuk melewati acara

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

Saya membuat codepen dengan sebuah contoh

Untuk memahami mengapa bendera baru diperlukan, saya menemukan komentar ini sangat membantu:

Logika masukan dalam React sekarang menghilangkan peristiwa perubahan sehingga tidak diaktifkan lebih dari sekali per nilai. Ini mendengarkan baik kejadian onChange / onInput browser serta set pada prop nilai simpul DOM (ketika Anda memperbarui nilai melalui javascript). Ini memiliki efek samping yang berarti bahwa jika Anda memperbarui nilai input secara manual input.value = 'foo' kemudian mengirimkan ChangeEvent dengan {target: input} React akan mendaftarkan set dan acara, lihat nilainya masih '' foo ', anggap itu acara duplikat dan telanlah.

Ini berfungsi dengan baik dalam kasus normal karena peristiwa yang dimulai browser "nyata" tidak memicu set pada element.value. Anda dapat keluar dari logika ini secara diam-diam dengan menandai peristiwa yang Anda picu dengan bendera simulasi dan bereaksi akan selalu mengaktifkan peristiwa tersebut. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128

Menyeringai
sumber
1
Terima kasih, ini berhasil dari react-dom ^ 15.6.0. Tapi sepertinya pasca React 16.0 ini telah berhenti bekerja. Adakah ide tentang alternatif untuk menggunakan bendera simulasi untuk memicu peristiwa perubahan?
Qwerty
Percaya ada satu hal di sini yang akan memberi petunjuk: reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Ada ide apa itu?
Qwerty
@Qwerty Saya memperbarui jawaban saya, mungkin itu akan berhasil untuk Anda
Grin
dan apa tentang tombol onClick? apa yang harus dilakukan untuk memicu peristiwa semacam ini?
Bender
@Bender Anda baru saja memanggil metode klik asli pada elemen
Grin
63

Setidaknya pada input teks, tampaknya onChangemendengarkan input event:

var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
Michael
sumber
Tergantung pada versi browser. IE8 tidak mendukung acara masukan. Dan ie9 tidak mengaktifkan peristiwa input saat Anda menghapus karakter dari kotak teks. developer.mozilla.org/en-US/docs/Web/Events/input
wallice
1
IE8 tidak lagi didukung oleh React. Untuk IE9, Anda mungkin dapat menggunakan sesuatu seperti var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, tetapi saya tidak memiliki VM IE9.
Michael
@ Michael Saya mencoba kode Anda di IE11 dan tidak bekerja pada bidang masukan reactjs. Ini berfungsi pada input HTML normal. Ini juga berfungsi di 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

Saya tahu jawaban ini datang sedikit terlambat tetapi saya baru-baru ini menghadapi masalah yang sama. Saya ingin memicu acara pada komponen bersarang. Saya memiliki daftar dengan radio dan widget tipe kotak centang (mereka div yang berperilaku seperti kotak centang dan / atau tombol radio) dan di beberapa tempat lain dalam aplikasi, jika seseorang menutup kotak alat, saya perlu menghapus centang satu.

Saya menemukan solusi yang cukup sederhana, tidak yakin apakah ini adalah praktik terbaik tetapi berhasil.

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

Ini memicu event klik pada domNode dan handler saya yang terpasang melalui react memang dipanggil sehingga berperilaku seperti yang saya harapkan jika seseorang mengklik elemen tersebut. Saya belum menguji onChange tetapi seharusnya berfungsi, dan tidak yakin bagaimana ini akan adil di versi IE yang benar-benar lama tetapi saya yakin MouseEvent didukung setidaknya di IE9 dan yang lebih baru.

Saya akhirnya pindah dari ini untuk kasus penggunaan khusus saya karena komponen saya sangat kecil (hanya sebagian dari aplikasi saya yang digunakan bereaksi karena saya masih mempelajarinya) dan saya dapat mencapai hal yang sama dengan cara lain tanpa mendapatkan referensi ke node dom.

MEMPERBARUI:

Seperti yang dinyatakan orang lain di komentar, lebih baik menggunakan this.refs.refnameuntuk mendapatkan referensi ke node dom. Dalam kasus ini, refname adalah referensi yang Anda lampirkan ke komponen melalui <MyComponent ref='refname' />.

Robert-W
sumber
1
Selain ID, Anda dapat menggunakan React.findDOMNodefungsi tersebut. goo.gl/RqccrA
m93a
2
> Sebagai ganti ID, Anda bisa menggunakan fungsi React.findDOMNode. Atau tambahkan a refke elemen Anda lalu gunakanthis.ref.refName.dispatchEvent
silkAdmin
Kerangka kerja kemungkinan besar telah berubah sejak itu, this.ref s .refname
nclord
1
Referensi string sekarang dianggap lama dan tidak akan digunakan lagi di masa mendatang. Referensi panggilan balik adalah metode yang disukai untuk melacak simpul DOM.
dzv3
9

Anda dapat mensimulasikan kejadian menggunakan ReactTestUtils tetapi itu dirancang untuk pengujian unit.

Saya akan merekomendasikan untuk tidak menggunakan valueLink untuk kasus ini dan hanya mendengarkan peristiwa perubahan yang dipicu oleh plugin dan memperbarui status input sebagai tanggapan. Util mengikat dua arah lebih sebagai demo dari apa pun; mereka disertakan dalam addons hanya untuk menekankan fakta bahwa pengikatan dua arah murni tidak sesuai untuk sebagian besar aplikasi dan bahwa Anda biasanya memerlukan lebih banyak logika aplikasi untuk mendeskripsikan interaksi dalam aplikasi Anda.

Sophie Alpert
sumber
Saya takut jawabannya akan seperti itu (. Masalahnya adalah React terlalu ketat dengan alur kerja kirim / terima. Secara khusus, seseorang harus menentukan penangan onChange untuk dapat bereaksi pada input pengguna, tidak ada cara lain kecuali keadaan tidak terkontrol yang baik over boilerplate kepada saya.Dalam kasus saya, saya harus mendeklarasikan handler ini hanya untuk mengikuti aturan, sedangkan input yang relevan akan datang dari acara onchange yang dipicu oleh jquery. React benar-benar tidak memiliki ide untuk memperluas titik akhir kirim / terima dengan kode yang dinyatakan pengguna
wallice
1
Dan ... jika Anda ingin menggunakan ReactTestUtils ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin
8

Memperluas jawaban dari Grin / Dan Abramov, ini berfungsi di berbagai jenis masukan. Diuji di 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);

    }

};
cjpete
sumber
5
Tidak berfungsi untuk elemen tertentu. Anda membutuhkan 'perubahan' alih-alih 'masukan' untuk acara tersebut.
Styks
3

Memicu peristiwa perubahan pada elemen arbitrer menciptakan ketergantungan antar komponen yang sulit untuk dipikirkan. Lebih baik tetap menggunakan aliran data satu arah React.

Tidak ada cuplikan sederhana untuk memicu peristiwa perubahan React. Logikanya diimplementasikan di ChangeEventPlugin.js dan ada cabang kode yang berbeda untuk jenis input dan browser yang berbeda. Selain itu, detail implementasi berbeda-beda di setiap versi React.

Saya telah membangun react-trigger-change yang melakukan hal itu, tetapi ini dimaksudkan untuk digunakan untuk pengujian, bukan sebagai ketergantungan produksi:

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

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

CodePen

Vitaly Kuznetsov
sumber
1

karena kami menggunakan fungsi untuk menangani acara onchange, kami dapat melakukannya seperti ini:

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)} />
  );
 }
}
empat-mata-04-04
sumber
1

Saya menemukan ini pada masalah Github React: Bekerja seperti pesona (v15.6.2)

Berikut adalah bagaimana saya mengimplementasikan ke input Teks:

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)
    }
  }
a2ron44
sumber
Ini tidak berfungsi jika nilai kotak teks sama dengan nilai yang Anda teruskan ke setNativeValue
Arun
0

Untuk HTMLSelectElement, yaitu<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);
oktohedron
sumber
-1

Jika Anda menggunakan Backbone dan React, saya akan merekomendasikan salah satu dari yang berikut ini,

Keduanya membantu mengintegrasikan model dan koleksi Backbone dengan tampilan React. Anda dapat menggunakan acara Backbone seperti yang Anda lakukan dengan tampilan Backbone. Saya telah mencoba keduanya dan tidak melihat banyak perbedaan kecuali satu adalah mixin dan perubahan lainnya React.createClasske React.createBackboneClass.

Blaine Hatab
sumber
Harap berhati-hati dengan jembatan "event-driven" antara react dan backbone. Pertama plugin menggunakan setProps mengabaikan level mount komponen. Itu salah. Kedua sangat bergantung pada forceUpdate dan pikiran bahwa hanya satu model yang dapat ditetapkan untuk sebuah komponen, yang bukan merupakan situasi umum. Terlebih lagi, jika Anda berbagi model di seluruh UI yang kompleks dengan komponen react dan lupa berhenti berlangganan dari peristiwa pembaruan yang tidak berguna yang menyebabkan forceUpdate, maka Anda bisa jatuh ke rendering rekursif, perhatikan hal itu.
wallice