Why React event handler is not called on dispatchEvent?
Asked Answered
N

4

31

Consider the following input element in a React component:

<input onChange={() => console.log('onChange')} ... />

While testing the React component, I'm emulating user changing the input value:

input.value = newValue;
TestUtils.Simulate.change(input);

This causes 'onChange' to be logged, as expected.

However, when the 'change' event is dispatched directly (I'm using jsdom):

input.value = newValue;
input.dispatchEvent(new Event('change'));

the onChange handler is not called.

Why?

My motivation to use dispatchEvent rather than TestUtils.Simulate is because TestUtils.Simulate doesn't support event bubbling and my component's behavior relies on that. I wonder whether there is a way to test events without TestUtils.Simulate?

Nolasco answered 21/8, 2016 at 13:57 Comment(0)
D
20

React uses its own events system with SyntheticEvents (prevents browser incompatabilities and gives react more control of events).

Using TestUtils correctly creates such a event which will trigger your onChange listener.

The dispatchEvent function on the other hand will create a "native" browser event. But the event handler you have is managed by react and therefore only reacts (badumts) to reacts SyntheticEvents.

You can read up on react events here: https://facebook.github.io/react/docs/events.html

Dneprodzerzhinsk answered 21/8, 2016 at 16:38 Comment(0)
E
19

One way to do it without ReactTestUtils.Simulate:

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://unpkg.com/react-trigger-change/dist/react-trigger-change.js';
document.head.appendChild(script);
input.value = value;
reactTriggerChange(input);

Look at the source of react-trigger-change to just cherry-pick what's needed. Example snippet:

if (nodeName === 'select' ||
    (nodeName === 'input' && type === 'file')) {
    // IE9-IE11, non-IE
    // Dispatch change.
    event = document.createEvent('HTMLEvents');
    event.initEvent('change', true, false);
    node.dispatchEvent(event);
  }
Euhemerize answered 27/7, 2017 at 17:2 Comment(6)
Not enough info, here. What is value? If I want to click a button, should that be added to const input or const input.value? And so I tried just setting it to const input, and got that reactTriggerChange is undefined, so I added an Import statement. Then got Cannot read property 'toLowerCase' of undefined. Plus I have a ton of linting errors I would have to clean up to use this package :( .Eliaseliason
@Eliaseliason added more descriptionEuhemerize
@Euhemerize Thanks-I pulled the last 3 lines out to use them in an event handler I made for a button to click another button. I assign node: const node = document.getElementsByClassName("close"); My target button has class="close" & it's the only element on the page with that class. When I click my 1st button that has these lines of code in it, gives me Uncaught TypeError: Cannot read property '__reactInternalInstance$iedoafjkjst1cmk11d5tgldi' of undefined - similar error to TestUtils.Simulate.click(node); #36752934Eliaseliason
@Euhemerize I think my problem was in how I was getting node. When I assigned an ID and used const node = document.getElementById("myID"), I got TestUtils.Simulate.click(node); to finally work. I tried again using the code above that used initEvent and dispatchEvent, and it stopped giving me errors, but it didn't work, either - nothing happened. React version 15.3.1. I dunno.Eliaseliason
React handles events at the document level, and that's the root cause for why your answer works. So you don't have to switch to the deprecated initEvent syntax - just adding {bubbles: true} as the second parameter of the Event constructor in the original code should work.Hidrosis
@JamesPearce Thanks. Can you edit the answer to add new approach?Euhemerize
S
5

The trick is to add { bubbles: true } to the created event. For example

target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
Sunlit answered 5/5, 2023 at 8:28 Comment(0)
B
4

I created a small version of the https://github.com/vitalyq/react-trigger-change only for the input element.

No external dependencies, copypaste and it will work.

Works for React 16.9, checked with Safari (14), Chrome (87), and Firefox (72).

const triggerInputChange = (node: HTMLInputElement, inputValue: string) => {
      const descriptor = Object.getOwnPropertyDescriptor(node, 'value');

      node.value = `${inputValue}#`;
      if (descriptor && descriptor.configurable) {
        delete node.value;
      }
      node.value = inputValue;

      const e = document.createEvent('HTMLEvents');
      e.initEvent('change', true, false);
      node.dispatchEvent(e);

      if (descriptor) {
        Object.defineProperty(node, 'value', descriptor);
      }
};
Bathometer answered 11/1, 2021 at 10:29 Comment(2)
how about <input type='checkbox' />, i replaced the value with 'checked' but doesn't workGains
This answer has a shorter version with less code: #23893047Metaprotein

© 2022 - 2024 — McMap. All rights reserved.