Why React event handler is not called on dispatchEvent?

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?

3 Answers

One way to do it without ReactTestUtils.Simulate:

var script = document.createElement('script'); script.type = 'text/javascript'; script.src = ' 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); } 
7

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:

1

I created a small version of the 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); } }; 
1

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like