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?

4 Answers 4

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

Sign up to request clarification or add additional context in comments.

Comments

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

6 Comments

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 :( .
@vapcguy added more description
@rofrol 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); stackoverflow.com/questions/36752434/…
@rofrol 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.
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.
|
6

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

target.dispatchEvent(new MouseEvent('click', { bubbles: true }))

Comments

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

2 Comments

how about <input type='checkbox' />, i replaced the value with 'checked' but doesn't work
This answer has a shorter version with less code: stackoverflow.com/questions/23892547/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.