Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dispatchEvent on input/textarea is ignored #10135

Closed
fatfisz opened this issue Jul 10, 2017 · 31 comments
Closed

dispatchEvent on input/textarea is ignored #10135

fatfisz opened this issue Jul 10, 2017 · 31 comments

Comments

@fatfisz
Copy link
Contributor

fatfisz commented Jul 10, 2017

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.

@gaearon
Copy link
Collaborator

gaearon commented Jul 11, 2017

cc @nhunzaker

@gaearon
Copy link
Collaborator

gaearon commented Jul 11, 2017

Oops, I meant to cc @jquense

@jquense
Copy link
Contributor

jquense commented Jul 11, 2017

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

@gaearon
Copy link
Collaborator

gaearon commented Jul 11, 2017

What's the workaround?

@gaearon
Copy link
Collaborator

gaearon commented Jul 11, 2017

I guess this is grey area and unclear if we supported this. As @jquense mentioned you can use SimulateNative in the meantime. After 16, feel free to file a new issue and we'll look at other ways of handling this.

@gaearon gaearon closed this as completed Jul 11, 2017
@jquense
Copy link
Contributor

jquense commented Jul 11, 2017

cypress-io/cypress#536 from an additional use case affected similarly. They outline their long term "workaround" as well.

I do think this falls into a gray area, complete transparent DOM interoperability is always nice but hard to do and potentially limiting give Reacts model for targeting and it :/

@fatfisz
Copy link
Contributor Author

fatfisz commented Jul 11, 2017

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

I understand that this is unexpected (the event should not be swallowed by React if the value has changed), so I'm including an updated example: https://jsfiddle.net/6bv1581z/1/

@fatfisz
Copy link
Contributor Author

fatfisz commented Jul 11, 2017

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.

@fatfisz
Copy link
Contributor Author

fatfisz commented Jul 11, 2017

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

@jquense
Copy link
Contributor

jquense commented Jul 11, 2017

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

@kentcdodds
Copy link
Contributor

Hi friends! I think I must be doing something wrong, but I have a pretty simple demo of the setNativeValue function shown above and I'm getting an error.

In the example I'm getting an error that seems to indicate that the textarea does not have a property descriptor for value. I've tried an input. And the prototype has the same issue. It sounds like this is working for a lot of people so I'm assuming that I'm probably making a silly mistake, but it's so simple I'm not sure what I could be doing wrong.

To save you a click, here's the whole example:

<!doctype html>
<html>

<body>
  <textarea></textarea>
  <script>
    const textarea = document.getElementsByTagName('textarea')[0]
    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)
      }
    }
    setNativeValue(textarea, 'some text')
    textarea.dispatchEvent(new Event('input', { bubbles: true }))
  </script>
</body>

</html>

What am I missing?

@kentcdodds
Copy link
Contributor

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!

@asliwinski
Copy link

@fatfisz @kentcdodds Could you please show how to do this for radio inputs?

@tkrotoff
Copy link

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

#11488 (comment)

/**
 * 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();

@addrexx
Copy link

addrexx commented Jul 19, 2019

Hi Everyone,
Many of these solutions are working well in all browser except IE 10. Has anyone come across a solution or tweak to make any of these functions for setting an input work in IE 10?
Thanks!

@hurrikam
Copy link

hurrikam commented Sep 3, 2019

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

Actually we're using a software implementation of the WebDriver interface in order to automate web apps on Smart TVs and other devices where no native WebDriver interface is available.
And we're hitting this issue indeed. :)

@gmattie
Copy link

gmattie commented Feb 25, 2020

In order to trigger an animation, my controlled text inputs were required to dispatch a change event by clearing their stateful values after the form data was validated and submitted. While the above solution works well, at least within my application, it was causing the following warning:

Warning: unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering.

Adding the dispatched event to the next available cycle in the event loop queue solved the issue:

//~

setTimeout(() => element.dispatchEvent(new Event("input", { bubbles: true })));

@kentcdodds
Copy link
Contributor

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

gmattie added a commit to gmattie/Ballot-Box that referenced this issue Feb 26, 2020
Implemented the AdminCredentials component and copied its ErrorResponse component styling to the Login component, which appropriately displays an "Invalid Credentials" error message outside of the relevant TextInput components group.  Also, added a SetNativeValue utility function to dispatch "change" events on TextInput components when their values are cleared by updates to their state.  For performance reasons React does not dispatch onChange or onInput events when a value of an HTMLElement is updated by state (RE: facebook/react#10135).
@cbdeveloper
Copy link

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

purfectliterature added a commit to purfectliterature/gradeassist that referenced this issue Aug 11, 2020
Coursemology uses React and simply changing the value attribute of the
multiplier input will not change Coursemology's state. As such, every
time an onChange event is fired from any text input (which in
Coursemology's case, all has), all the values are re-rendered (since
changing grades and multipliers ultimately affect EXP, and they must be
displayed. This was an issue raised in React in 2017, but ultimately
deemed as a security feature. To solve this issue, we will have to
directly invoke the onChange event listener in the input prototype
(as prescribed in coursemology.js in webpack output). Read more here
facebook/react#10135 (comment)
@itsalivenger
Copy link

i have tried this with tiktok input for comment but they have a span nested in div continuously and the setNativeValue doesn't work for it because divs and spans doesn't have a .set property... anyone has any idea to make it work ?

@obelixotic
Copy link

Hi! Anyone tried the same thing for drop down menus built with React?

@BonBonSlick
Copy link

BonBonSlick commented Feb 10, 2022

Hello, anyone knows why it all salvations above are not working with Redux state? https://codepen.io/eqwfsdgfdg/pen/MWOmaQJ

@gaganmeet
Copy link

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?

@locxiang
Copy link

locxiang commented Aug 16, 2022

I need to know too. ;Is there a similar workaround if its a contentEditable div?

@sohamsarkar1993
Copy link

bumping this for contentEditable div

@maxwell-git
Copy link

maxwell-git commented May 15, 2023

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

@mehdijalili2000
Copy link

mehdijalili2000 commented Feb 9, 2024

Unfortunately, the proposed solutions did not work for Textara. The page for which I need this code is designed with React and works for all elements except textarea. I want to automatically enter values in the elements in Firefox and from the console. help me please

@pwbriggs
Copy link

Hey @mehdijalili2000, 👋

This is an old issue, and React has been updated since. How about you open a new issue for this bug?

Thanks! ❤️

@mehdijalili2000
Copy link

mehdijalili2000 commented Feb 13, 2024 via email

@pwbriggs
Copy link

Cross-reference for future readers: see #28311.

@mehdijalili2000
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests