181

We use Backbone + ReactJS bundle to build a client-side app. Heavily relying on notorious valueLink we propagate values directly to the model via own wrapper that supports ReactJS interface for two way binding.

Now we faced the problem:

We have jquery.mask.js plugin which formats input value programmatically thus it doesn't fire React events. All this leads to situation when model receives unformatted values from user input and misses formatted ones from plugin.

It seems that React has plenty of event handling strategies depending on browser. Is there any common way to trigger change event for particular DOM element so that React will hear it?

wallice
  • 2,004
  • 2
  • 13
  • 12
  • 2
    React Github discussion: [Trigger simulated input value change for React 16](https://github.com/facebook/react/issues/11488) – vsync Apr 24 '20 at 18:32

14 Answers14

334

For React 16 and React >=15.6

Setter .value= is not working as we wanted because React library overrides input value setter but we can call the function directly on the input as context.

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

For textarea element you should use prototype of HTMLTextAreaElement class.

New codepen example.

All credits to this contributor and his solution

Outdated answer only for React <=15.5

With react-dom ^15.6.0 you can use simulated flag on the event object for the event to pass through

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

I made a codepen with an example

To understand why new flag is needed I found this comment very helpful:

The input logic in React now dedupe's change events so they don't fire more than once per value. It listens for both browser onChange/onInput events as well as sets on the DOM node value prop (when you update the value via javascript). This has the side effect of meaning that if you update the input's value manually input.value = 'foo' then dispatch a ChangeEvent with { target: input } React will register both the set and the event, see it's value is still `'foo', consider it a duplicate event and swallow it.

This works fine in normal cases because a "real" browser initiated event doesn't trigger sets on the element.value. You can bail out of this logic secretly by tagging the event you trigger with a simulated flag and react will always fire the event. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128

Soorena
  • 4,352
  • 5
  • 30
  • 42
Grin
  • 6,082
  • 2
  • 22
  • 15
  • 3
    Thanks this did work from react-dom ^15.6.0. But it seems post React 16.0 this has stopped working. Any idea on the alternative to using simulated flag to trigger the change events? – Qwerty Nov 07 '17 at 04:57
  • Believe there is a point here which would give the hint: https://reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Any idea what it could be? – Qwerty Nov 08 '17 at 05:36
  • @Qwerty I updated my answer, may be it'll work for you – Grin Nov 17 '17 at 08:35
  • and what abou button onClick? what sould be done to trigger this kind of event? – Bender Apr 02 '19 at 07:10
  • 1
    @Bender you just call native [click](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click) method on element – Grin Apr 03 '19 at 12:33
  • @Grin sure, thank you, some times I'm just overcomplicating – Bender Apr 11 '19 at 06:44
  • @Grin for me this is working if I use nativeInputValueSetter.call(input, "01.06.2019"); but I really want to use the value from the input itself: nativeInputValueSetter.call(input, input.value); But this is not calling the onChange of react... Do You know why this is? – G43beli May 15 '19 at 09:56
  • @G43beli probably because old and new values are equal. If you need to use current value you can save it to variable and reset value on input like so `var curValue = input.value; input.value = ''; nativeInputValueSetter.call(input, curValue);` and then dispatch input event – Grin Aug 30 '19 at 10:18
  • Worked for me on an angular site as well, Thanks! – Shehroz Ahmed Apr 26 '21 at 22:08
  • For those who are wondering about what is the variable `input`, it can be a `ref` so if you have assigned like `` then `input` variable will be `inputRef.current`. – Shivam Sharma Oct 14 '21 at 13:28
  • 1
    I get a `TypeError: Illegal invocation` on `textarea` elements. Juste changing `window.HTMLInputElement` by `window.HTMLTextAreaElement.prototype` did the tricks. Thank you – OOM Feb 19 '22 at 18:27
  • This works for MOBX as well in 2023 – mplungjan Feb 05 '23 at 19:52
73

At least on text inputs, it appears that onChange is listening for input events:

var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
Konstantin Grushetsky
  • 1,012
  • 1
  • 15
  • 32
Michael
  • 1,819
  • 14
  • 4
  • Depends on the browser version. IE8 doesn't support input event. And ie9 doesn't fire input event when u remove characters from the textbox. https://developer.mozilla.org/en-US/docs/Web/Events/input – wallice Jul 11 '15 at 09:23
  • `Event` is not support IE [https://developer.mozilla.org/en-US/docs/Web/API/Event/Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) – Joyful Mar 07 '16 at 09:18
  • 2
    IE8 [is no longer supported](https://facebook.github.io/react/blog/2016/01/12/discontinuing-ie8-support.html) by React. For IE9, you may be able to use something like `var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });`, but I don't have a IE9 VM handy. – Michael Jul 21 '16 at 20:07
  • @Michael I'm trying your code on IE11 and it's not working on reactjs input fields. It does work on normal HTML inputs. It also works on Edge. `var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('input', true, false, { }); document.getElementById('description').value = 'I changed description'; document.getElementById('description').dispatchEvent(evt);` – Bodosko May 10 '17 at 16:42
  • This does not work for MOBX – mplungjan Feb 05 '23 at 19:52
26

Expanding on the answer from Grin/Dan Abramov, this works across multiple input types. Tested in 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
  • 491
  • 4
  • 9
  • 10
    Does not work for select elements. You need 'change' instead of 'input' for the event. – styks May 15 '18 at 20:07
  • Just so people are aware, `__proto__` is deprecated according to the docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto You can use Object.getPrototypeOf(node). constructor instead :) – ii iml0sto1 Nov 08 '22 at 08:55
21

I know this answer comes a little late but I recently faced a similar problem. I wanted to trigger an event on a nested component. I had a list with radio and check box type widgets (they were divs that behaved like checkboxes and/or radio buttons) and in some other place in the application, if someone closed a toolbox, I needed to uncheck one.

I found a pretty simple solution, not sure if this is best practice but it works.

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

This triggered the click event on the domNode and my handler attached via react was indeed called so it behaves like I would expect if someone clicked on the element. I have not tested onChange but it should work, and not sure how this will fair in really old versions of IE but I believe the MouseEvent is supported in at least IE9 and up.

I eventually moved away from this for my particular use case because my component was very small (only a part of my application used react since i'm still learning it) and I could achieve the same thing another way without getting references to dom nodes.

UPDATE:

As others have stated in the comments, it is better to use this.refs.refname to get a reference to a dom node. In this case, refname is the ref you attached to your component via <MyComponent ref='refname' />.

Robert-W
  • 1,541
  • 12
  • 16
  • 1
    Instead of ID, you can use the `React.findDOMNode` function. http://goo.gl/RqccrA – m93a Apr 03 '15 at 12:10
  • 3
    >Instead of ID, you can use the React.findDOMNode function. Or add a `ref` to your element then use `this.ref.refName.dispatchEvent` – silkAdmin Dec 17 '15 at 02:52
  • Framework has most likely changed since, its this.ref**s**.refname – nclord Jan 21 '16 at 14:49
  • 1
    String refs are considered legacy now and will be deprecated in the future. Callback refs are the preferred method to keep track of the DOM node. – Diego Zacarias Oct 07 '16 at 11:50
9

You can simulate events using ReactTestUtils but that's designed for unit testing.

I'd recommend not using valueLink for this case and simply listening to change events fired by the plugin and updating the input's state in response. The two-way binding utils more as a demo than anything else; they're included in addons only to emphasize the fact that pure two-way binding isn't appropriate for most applications and that you usually need more application logic to describe the interactions in your app.

Sophie Alpert
  • 139,698
  • 36
  • 220
  • 238
  • I was afraid that answer will be something like that (. Problem is that React is too strict with send/receive workflow. In particular one must specify onChange handler to be able to react on user input,no other way except uncontrolled state which is kind of over boilerplate to me.In my case I should declare this handler only to follow the rules, while relevant input will come from onchange event triggered by jquery. React really lacks idea of extending send/receive endpoints with user declared code – wallice May 28 '14 at 07:09
  • 1
    And... in case you want to use ReactTestUtils... `ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))` – colllin Jan 13 '16 at 22:34
8

For HTMLSelectElement, i.e. <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);
Computer's Guy
  • 5,122
  • 8
  • 54
  • 74
6

I stumbled upon the same issue today. While there is default support for the 'click', 'focus', 'blur' events out of the box in JavaScript, other useful events such as 'change', 'input' are not implemented (yet).

I came up with this generic solution and refactored the code based on the accepted answers.

export const triggerNativeEventFor = (elm, { event, ...valueObj }) => {
  if (!(elm instanceof Element)) {
    throw new Error(`Expected an Element but received ${elm} instead!`);
  }

  const [prop, value] = Object.entries(valueObj)[0] ?? [];
  const desc = Object.getOwnPropertyDescriptor(elm.__proto__, prop);

  desc?.set?.call(elm, value);
  elm.dispatchEvent(new Event(event, { bubbles: true }));
};

How does it work?

triggerNativeEventFor(inputRef.current, { event: 'input', value: '' });

Any 2nd property you pass after the 'event' key-value pair, it will be taken into account and the rest will be ignored/discarded. This is purposedfully written like this in order not to clutter arguments definition of the helper function. The reason as to why not default to get descriptor for 'value' only is that for instance, if you have a native checkbox <input type="checkbox" />, than it doesn't have a value rather a 'checked' prop/attribute. Then you can pass your desired check state as follows:

triggerNativeEventFor(checkBoxRef.current, { event: 'input', checked: false });
4

I found this on React's Github issues: Works like a charm (v15.6.2)

Here is how I implemented to a Text input:

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
  • 1,711
  • 1
  • 13
  • 18
  • This doesn't work if the textbox value is the same as the value you are passing to setNativeValue – Arun Apr 09 '19 at 02:38
4

A working solution can depend a bit on the implementation of the onChange function you're trying to trigger. Something that worked for me was to reach into the react props attached to the DOM element and call the function directly.

I created a helper function to grab the react props since they're suffixed with a hash like .__reactProps$fdb7odfwyz

It's probably not the most robust but it's good to know it's an option.

function getReactProps(el) {
  const keys = Object.keys(el);
  const propKey = keys.find(key => key.includes('reactProps'));
  return el[propKey];
}

const el = document.querySelector('XX');
getReactProps(el).onChange({ target: { value: id }  });

Since the onChange function was only using target.value I could pass a simple object to onChange to trigger my change.


This method can also help with stubborn react owned DOM elements that are listing for onMouseDown and do not respond to .click() like you'd expect.

getReactProps(el).onMouseDown(new Event('click'));
spencer.sm
  • 19,173
  • 10
  • 77
  • 88
3

Triggering change events on arbitrary elements creates dependencies between components which are hard to reason about. It's better to stick with React's one-way data flow.

There is no simple snippet to trigger React's change event. The logic is implemented in ChangeEventPlugin.js and there are different code branches for different input types and browsers. Moreover, the implementation details vary across versions of React.

I have built react-trigger-change that does the thing, but it is intended to be used for testing, not as a production dependency:

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

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

CodePen

Vitaly Kuznetsov
  • 1,515
  • 1
  • 16
  • 15
1

This ugly solution is what worked for me:

let ev = new CustomEvent('change', { bubbles: true });
Object.defineProperty(ev, 'target', {writable: false, value: inpt });
Object.defineProperty(ev, 'currentTarget', {writable: false, value: inpt });
const rHandle = Object.keys(inpt).find(k => k.startsWith("__reactEventHandlers"))
inpt[rHandle].onChange(ev);
user5480949
  • 1,410
  • 1
  • 15
  • 22
0

well since we use functions to handle an onchange event, we can do it like this:

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)} />
  );
 }
}
aprilmintacpineda
  • 1,114
  • 13
  • 21
0

The Event type input did not work for me on <select> but changing it to change works

useEffect(() => {
    var event = new Event('change', { bubbles: true });
    selectRef.current.dispatchEvent(event); // ref to the select control
}, [props.items]);
Shashank Shekhar
  • 3,958
  • 2
  • 40
  • 52
-1

If you are using Backbone and React, I'd recommend one of the following,

They both help integrate Backbone models and collections with React views. You can use Backbone events just like you do with Backbone views. I've dabbled in both and didn't see much of a difference except one is a mixin and the other changes React.createClass to React.createBackboneClass.

Bart Riordan
  • 436
  • 3
  • 8
  • 23
Blaine Hatab
  • 1,626
  • 17
  • 24
  • Please, be careful with those "event-driven" bridges between react and backbone First plugin uses setProps disregard of component mount level. It's a mistake. Second heavily relies on forceUpdate and minds that only one model could be assigned for a component, which is not common situation. More over, if u share model across complex UI with react components and have had forgotten to unsubscribe from useless update events that cause forceUpdate, then you can fall into recursive rendering, be aware about that. – wallice Sep 12 '14 at 08:52