react: dispatchEvent on input/textarea is ignored

Do you want to request a feature or report a bug? bug

What is the current behavior? The dispatchEvent method has no effect on input/textarea elements.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://jsfiddle.net or similar (template: https://jsfiddle.net/84v837e9/). v. 15.5.4: https://jsfiddle.net/c8tp5mqf/ (working) v. 15.6.1: https://jsfiddle.net/6bv1581z/ (not working)

What is the expected behavior? The dispatchEvent method results in an event being handled.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React? I’m using the latest Chrome browser. This has worked in 15.5.x, stopped working in 15.6.0 and is still not working in 15.6.1.


Usecase for this: some old tests that I’d happily remove, but have to support for now.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 31 (9 by maintainers)

Commits related to this issue

Most upvoted comments

Just leaving a solution for future reference (checked in Edge 15, IE 11, FF 53, Chrome 59):

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

Use it like so:

setNativeValue(textarea, 'some text');
textarea.dispatchEvent(new Event('input', { bubbles: true }));

I’m not sure why I missed this or why this is, but it looks like there actually is a value setter on the prototype but there isn’t always one on the element. So here’s my adjusted version that works:

<!doctype html>
<html>

<body>
  <textarea></textarea>
  <script>
    const textarea = document.getElementsByTagName('textarea')[0]
    function setNativeValue(element, value) {
      const { set: valueSetter } = Object.getOwnPropertyDescriptor(element, 'value') || {}
      const prototype = Object.getPrototypeOf(element)
      const { set: prototypeValueSetter } = Object.getOwnPropertyDescriptor(prototype, 'value') || {}

      if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
        prototypeValueSetter.call(element, value)
      } else if (valueSetter) {
        valueSetter.call(element, value)
      } else {
        throw new Error('The given element does not have a value setter')
      }
    }
    setNativeValue(textarea, 'some text')
    textarea.dispatchEvent(new Event('input', { bubbles: true }))
  </script>
</body>

</html>

Good luck friends. Stay safe out there!

Found another issue talking about this that proposes an alternative solution:

https://github.com/facebook/react/issues/11488#issuecomment-347775628

/**
 * See [Modify React Component's State using jQuery/Plain Javascript from Chrome Extension](https://stackoverflow.com/q/41166005)
 * See https://github.com/facebook/react/issues/11488#issuecomment-347775628
 * See [How to programmatically fill input elements built with React?](https://stackoverflow.com/q/40894637)
 * See https://github.com/facebook/react/issues/10135#issuecomment-401496776
 *
 * @param {HTMLInputElement} input
 * @param {string} value
 */
function setReactInputValue(input, value) {
  const previousValue = input.value;

  // eslint-disable-next-line no-param-reassign
  input.value = value;

  const tracker = input._valueTracker;
  if (tracker) {
    tracker.setValue(previousValue);
  }

  // 'change' instead of 'input', see https://github.com/facebook/react/issues/11488#issuecomment-381590324
  input.dispatchEvent(new Event('change', { bubbles: true }));
}

Usage:

setReactInputValue(document.getElementById('name'), 'Your name');
document.getElementById('radio').click();
document.getElementById('checkbox').click();

I’m not sure why I missed this or why this is, but it looks like there actually is a value setter on the prototype but there isn’t always one on the element. So here’s my adjusted version that works:

<!doctype html>
<html>

<body>
  <textarea></textarea>
  <script>
    const textarea = document.getElementsByTagName('textarea')[0]
    function setNativeValue(element, value) {
      const { set: valueSetter } = Object.getOwnPropertyDescriptor(element, 'value') || {}
      const prototype = Object.getPrototypeOf(element)
      const { set: prototypeValueSetter } = Object.getOwnPropertyDescriptor(prototype, 'value') || {}

      if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
        prototypeValueSetter.call(element, value)
      } else if (valueSetter) {
        valueSetter.call(element, value)
      } else {
        throw new Error('The given element does not have a value setter')
      }
    }
    setNativeValue(textarea, 'some text')
    textarea.dispatchEvent(new Event('input', { bubbles: true }))
  </script>
</body>

</html>

Good luck friends. Stay safe out there!

Is there a similar workaround if its a contentEditable div?

Btw. this happens no matter if the value is changed or not - I probably should’ve included that info in this issue.

Yes, i also left that bit off because its a bit hard to explain, but React tracks manual DOMNode.value changes as well, so when you do input.value ='foo' and then dispatch a change event React sees two discreet moments there, the first change and then the change via the event, so when your input event fires the value is still 'foo' and React says “I already know about that that value so this must be a duplicate”

I’m just wondering - won’t this affect the ability to make integration tests for React-based apps using the DOM api?

It shouldn’t, if you are using something like Selenium, which fires “real” events when you ask it too, vs “fake” ones we can fire in the DOM. Comparative tools like Cypress or nightmare, etc should use workarounds that mimic the real browsers behavior so if something breaks (as in this case) it was more of a bug in Cypress 😃

I just tried the _valueTracker solution and it does trigger the onChange handler. But I get caret position jump.

Here is my use case:

  • User types “,” (comma) // I need to replace it with a "." (point)
  • onKeyDown does event.preventDefault()
  • onKeyDown ideally would fire a KeyBoard event that behaves exactly like if the user had typed “.” (point), so that I don’t get caret jumping issues.

Is this by any means possible?

Because if I’m going to deal with caret jumping anyway, I can always call my onChange function directly from my onKeyDown.

What I did so far:

function onKeyDown(event) {
    if (event.key === ",") {
      event.preventDefault();
      const previousValue = input_ref.current.value;
      const caretPosition = input_ref.current.selectionStart;
      const point = ".";
      const newValue = [previousValue.slice(0, caretPosition), point, previousValue.slice(caretPosition)].join('');
      
      input_ref.current.value = newValue;

      const tracker = input_ref.current._valueTracker;

      if (tracker) {
        tracker.setValue(previousValue);
      }

      input_ref.current.dispatchEvent(new Event('change', { bubbles: true }));
    }
  }

the code next is worked for me.

// a login form simulate 
const $ = document.querySelector.bind(document);
const username = $('[name=username]');
username.value = 'your username';
const password = $('[name=password]');
password.value = 'userpassword';
const button = $('button');

// find the key
const key = Object.keys(username).find(key=>key.includes('__reactEventHandlers'));
// invoke onchange
username[key].onChange({target:username});
password[key].onChange({target:password});

button.click()

I’ll need some time to process the use case in cypress (esp. the part about the setters - don’t really know what the spec says about this), so I’m not strongly opinionated for now.

In any case, the simulated thing seems to be working for now and the code that’s using it waits for a rewrite, so that might be turned into a non-issue for me.

I’m just wondering - won’t this affect the ability to make integration tests for React-based apps using the DOM api?

Sorry if that’s all answered in the linked thread, I have yet to connect the dots.

hi. I know has been updated but i have this problem.

On Tue, Feb 13, 2024 at 2:48 AM Peter Briggs @.***> wrote:

Hey @mehdijalili2000 https://github.com/mehdijalili2000, 👋

This is an old issue, and React has been updated since. How about you open a new issue https://github.com/facebook/react/issues/new/choose for this bug?

Thanks! ❤️

— Reply to this email directly, view it on GitHub https://github.com/facebook/react/issues/10135#issuecomment-1939768137, or unsubscribe https://github.com/notifications/unsubscribe-auth/ATOY3EIMKW2NQBJWSGV4EKTYTKPK3AVCNFSM4DSMMFZKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOJTHE3TMOBRGM3Q . You are receiving this because you were mentioned.Message ID: @.***>

Oh, kinda forgot about this issue, but now that I’m here I thought I’d mention that the change event in DOM Testing Library’s fireEvent has this built-in 😃

Yes this is expected and unavoidable to some extent. React dedupe change and input events so they don’t fire too often, in this case even tho you intentionally want to trigger the event react is swallowing it because it the input value has not changed. you can use the SimulateNative helper to get the behavior your looking for. Tho that will break again in v16