react: Trigger simulated input value change for React 16 (after react-dom 15.6.0 updated)?

I’m trying to trigger the input/change event on a React form outside react using pure JS or jQuery. With react-dom 15.6.0 you were able to use simulated flag on the event object for the event to pass through

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

Note I cannot use React even though the form trying to trigger on is based on React, have to pure JS or jQuery to trigger the input value change event.

So the original suggestion from this comment used to work: https://github.com/cypress-io/cypress/issues/536#issuecomment-308739206

But after React 16 release this is not triggering the input and change event as expected.

What are the internal changes as to how it handles changes to input data in React 16?

Believe there is a point here which would give the hint: reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-change‌​s Any idea what it could be?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 20
  • Comments: 39 (4 by maintainers)

Commits related to this issue

Most upvoted comments

After some research of react source code, I got a hack method for react 16:

let input = someInput; 
let lastValue = input.value;
input.value = 'new value';
let event = new Event('input', { bubbles: true });
// hack React15
event.simulated = true;
// hack React16 内部定义了descriptor拦截value,此处重置状态
let tracker = input._valueTracker;
if (tracker) {
  tracker.setValue(lastValue);
}
input.dispatchEvent(event);

NOTICE: JUST A HACK

Can you just call setState on the component instance that renders the input to have it take a different value?

Basically I have to use pure JS or jQuery (without React access) to enter some values in some input fields within a form rendered by React 16.

Can you expand on why you need this? It is not exactly clear what your constraints are.

More general (or maybe less hacky) way is described here: https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-onchange-event-in-react-js#46012210

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

PS. I’m not the author.

this worked for me. changing a select menu on a compiled react page, and making sure the appropriate change events fire.

input = $('#some-id')[0];
input.selectedIndex = 3; //or whatever the new selection should be
event = new Event('change', { bubbles: true });
$(input).dispatchEvent(event); #react on change events fire

This is very needed for Selenium tests automation - it’s hard to set value for input type color

For me the event type input did not work on some applications as @whosesmile suggested so I found out that change works better:

let event = new Event('change', { bubbles: true });

After some research of react source code, I got a hack method for react 16:

let input = someInput; 
let lastValue = input.value;
input.value = 'new value';
let event = new Event('input', { bubbles: true });
// hack React15
event.simulated = true;
// hack React16 内部定义了descriptor拦截value,此处重置状态
let tracker = input._valueTracker;
if (tracker) {
  tracker.setValue(lastValue);
}
input.dispatchEvent(event);

NOTICE: JUST A HACK

With text input (text area included) the event “input” works, but not “change”. With select tags the event “change” works but not “input”. Thanks again.

After some research of react source code, I got a hack method for react 16:

let input = someInput; 
let lastValue = input.value;
input.value = 'new value';
let event = new Event('input', { bubbles: true });
// hack React15
event.simulated = true;
// hack React16 内部定义了descriptor拦截value,此处重置状态
let tracker = input._valueTracker;
if (tracker) {
  tracker.setValue(lastValue);
}
input.dispatchEvent(event);

NOTICE: JUST A HACK

Good job figuring out this hack!

Thx all!

This is what I ended up using in typescript:

const forceReactInputOnChange = (input: HTMLInputElement) => {
  // @ts-expect-error NOTE: clear the interal value to force an actual change
  input._valueTracker?.setValue("");
  input.dispatchEvent(new Event("input", { bubbles: true }));
};

After some research of react source code, I got a hack method for react 16:

let input = someInput; 
let lastValue = input.value;
input.value = 'new value';
let event = new Event('input', { bubbles: true });
// hack React15
event.simulated = true;
// hack React16 内部定义了descriptor拦截value,此处重置状态
let tracker = input._valueTracker;
if (tracker) {
  tracker.setValue(lastValue);
}
input.dispatchEvent(event);

NOTICE: JUST A HACK

You have no idea how long I have been searching for this. THANK YOU.

@gaearon I have the most basic possible use case for this, I’m making a typical input with clear button component, to be a drop-in replacement for an <input>. Like the <input> it wraps, it communicates with the outside world via value and onChange. Clicking the clear button needs to somehow call onChange with a valid event where event.currentTarget.value is ''. Feels like this ought to be easy and not require any hacks.

Calling setState on whatever would break encapsulation since this component is supposed to be as dumb as <input>.

@whosesmile what is _valueTracker? cant find it in any docs

It seems like this workaround broke in 16.13 for us. Did anyone else run into issues with upgrading?

Browser extensions expect to be able to influence form values.

It is to interact with a React based website’s form from a proprietary web browser which has to use the external form. The browser has limited access to external scripts. It is just one of those legacy systems that has to interact with a modern SPA web page to trigger a form submission. The legacy system which supports JS and jQuery may be deprecated. But really just need to figure out how to trigger this input value change as it worked in the past. I can at most put inline JS code or possibly a external JS URL to refer to external library if I have use ReactTestUtils as a simulate method. Was hoping to not be in this situation, but trying to resolve with what is possible in the interim.

Generic method for changing input in React (Shamelessly taken from here)

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

    }

};

Answers in this thread weren’t working for me when editing a TextArea, but this does!

Also to @gaearon some of us are building browser extensions, which can come with a plethora of constraints

I’ve found the _valueTracker.setValue “trick” to be perfectly working with only:

inputElm._valueTracker.setValue('')

input = $(‘#some-id’)[0]; input.selectedIndex = 3; //or whatever the new selection should be event = new Event(‘change’, { bubbles: true }); $(input).dispatchEvent(event); #react on change events fire

Just needed a little cleanup and this worked for me.

@whosesmile thanks for the hack! My use case is I have a bunch of bookmark scripts that do things such as auto-login, or auto-fill forms with various test data. When you have a constant stream of new development servers, autofill usually doesn’t work. I’ve had to update these scripts several times with new React versions.

You can put the reference to your component onto the input, for example.

Yep I could, but how can do that using the console or outside the React scope? for example in this codepen: https://codepen.io/gkitarp/pen/pdNRKP

I don’t have direct access to the React component, only the global scope of console is used. So in the example the //Outside the React scope is what I have access to and can edit.

Here’s a sandbox demonstrating the ClearableInput I want (currently using the hack outlined above): https://codesandbox.io/s/brave-ganguly-cp6m7?file=/src/ClearableInput.js

A good example of where this might be necessary is when interacting with https://github.com/escaladesports/react-hubspot-form. The component interface does not provide any way to initialize values for a form, the component does not store input values in “state” so ref.setState isn’t an option, and the onReady callback only returns a jquery form object that points to an iframe(?!).

Anyway, the only way (I’ve found) to initialize this form is to do so by manipulating the inputs on the DOM and manually triggering react to pick up the changes.

FWIW: I would agree that this is a problem with the react-hubspot-form interface (https://github.com/escaladesports/react-hubspot-form/issues/13), and not necessarily with React itself (I’ll leave that to you all). I just thought I’d provide some context.

Thanks for the response! So the full isolated simple example is as follows: Using React 15.6.1 input: https://codepen.io/gkitarp/pen/LObxVd Using React 16.0.0 input: https://codepen.io/gkitarp/pen/pdNRKP As per the example, when you click on the submit button on React 16 the value has not been changed, but on React 15.6.1 it works due to the use of simulated dispatch event.

Basically I have to use pure JS or jQuery (without React access) to enter some values in some input fields within a form rendered by React 16. Earlier the form and input fields was rendered with React 15.6.x but after the form/site updated to React 16 the pure JS way of simulating input value change stopped working.

So looking to quickly know how to trigger or simulate input value update so it follows the right dispatch event sequence or method in React 16.

I’m unsure if I can use ReactTestUtils as a simulate method just by injecting the react and react-dom 16.0.0 standard scripts as I don’t have access to the original form’s React implementation. I prefer to trigger this with HTML dom and JS. But guess that is unreliable so wondering if using ReactTestUtils as a standalone to trigger input change will work?