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

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

Closed
gkitarp opened this issue Nov 8, 2017 · 39 comments

Comments

@gkitarp
Copy link

gkitarp commented Nov 8, 2017

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: cypress-io/cypress#536 (comment)

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?

@gaearon
Copy link
Collaborator

gaearon commented Nov 8, 2017

I'm not sure if that simulated flag was even intentionally supported. I think it was used as a private marker and never intended to be public API. So you relied on implementation details that changed. (Granted, @jquense suggested that, but I still don't think this was a public API.)

Can you provide a full isolated example of what you're trying to do? Maybe this answer could be helpful: #10003 (comment)

@gkitarp
Copy link
Author

gkitarp commented Nov 8, 2017

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?

@gaearon
Copy link
Collaborator

gaearon commented Nov 8, 2017

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.

@gkitarp
Copy link
Author

gkitarp commented Nov 8, 2017

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.

@gaearon
Copy link
Collaborator

gaearon commented Nov 8, 2017

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

@gkitarp
Copy link
Author

gkitarp commented Nov 8, 2017

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.

@gaearon
Copy link
Collaborator

gaearon commented Nov 8, 2017

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

@jquense
Copy link
Contributor

jquense commented Nov 8, 2017

yeah not intended as a public API, its wasn't even intended to make it into v16, it was a workaround to prevent a breaking change in v15 minor bump. We should have a better story for this perhaps, but "correct" way to work around this is outlined here: cypress-io/cypress#536 (comment)

There should be vanishingly few use-cases though where you need to get underneath the event system like this. I'm not suggesting you don't have one of those use-cases, but i would try almost anything else before this :P since this is generally outside what i'd consider "reasonable" interop of none react interactions

@gkitarp gkitarp closed this as completed Nov 13, 2017
@whosesmile
Copy link

whosesmile commented Nov 29, 2017

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

@mhuusko5
Copy link

mhuusko5 commented Jan 11, 2018

@whosesmile any way to get this to work with '.selectedIndex' instead of '.value'? Or just on a in general, even if applying change to '.value' (not working for me)?

@matinfazli
Copy link

matinfazli commented Apr 16, 2018

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

@morsdyce
Copy link

morsdyce commented May 9, 2018

See this for a solution:
#10135 (comment)

@spnkr
Copy link

spnkr commented Jun 28, 2018

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

@afraser
Copy link

afraser commented Aug 30, 2018

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 (escaladesports/react-hubspot-form#13), and not necessarily with React itself (I'll leave that to you all). I just thought I'd provide some context.

@mpearson
Copy link

mpearson commented Sep 11, 2018

@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.

@artyomtrityak
Copy link

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

@stevejboyer
Copy link

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.

@jchook
Copy link

jchook commented Jan 6, 2019

Browser extensions expect to be able to influence form values.

@jacobrask
Copy link

The reason we need native change events is that we have a modal form with a change listener, if a change event has bubbled up to the form we consider the modal dirty. If the modal is dirty we show a confirm dialog when closing the modal, otherwise not.

@joehany
Copy link

joehany commented Jan 31, 2019

@whosesmile you're a king.. thank you.

@undrafted
Copy link

undrafted commented Feb 20, 2019

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

@AndyNovo
Copy link

AndyNovo commented Mar 7, 2019

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.

@rodrigograca31
Copy link
Contributor

@AndyNovo thanks! FINALLY! after hours and hours trying to make React keep my input value changes your code did it!

@zrk
Copy link

zrk commented Nov 27, 2019

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.

@CShabolotova
Copy link

CShabolotova commented Feb 10, 2020

This is very needed for Selenium tests automation - it's hard to set value for input type color
@artyomtrityak How did you handle this issue?

@jljorgenson18
Copy link

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

@jedwards1211
Copy link
Contributor

jedwards1211 commented Apr 14, 2020

@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>.

@jedwards1211
Copy link
Contributor

jedwards1211 commented Apr 14, 2020

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

@jljorgenson18
Copy link

I ended up using the Simulate instance in react-dom/test-utils (which felt pretty wrong because this is production code but I couldn't find another way to do it)

    if (input._valueTracker) {
        Simulate.change(input);
    } else {
        input.dispatchEvent(new Event('change', { bubbles: true }));
    }

@yairEO
Copy link

yairEO commented Apr 24, 2020

My simple scenario:

I must find a fix to trigger change event for my vanilla component, which has a React Wrapper for it:

https://github.com/yairEO/tagify

When using the component through the React wrapper, a developer would write code like this:

<Tags
  value="a,b,c"
  placeholder="type..."
  onChange={e => console.log("CHANGED:", e)}
/>

Then then wrapper is creating an input element and an instance of the vanilla Tagify is instantiated and applied on the created input element.

The onChange prop is applied as an attribute to the input, in the wrapper, as so:

const attrs = {
  ...
  onChange: this.props.onChange || function(){}
}

return React.createElement("div", {className},
  React.createElement("input", Object.assign({}, attrs))
)

@SharlSherif
Copy link

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.

@yairEO
Copy link

yairEO commented May 30, 2020

I've found the _valueTracker.setValue "trick" to be perfectly working with only:

inputElm._valueTracker.setValue('')

@kangzj

This comment has been minimized.

@benvds
Copy link

benvds commented Jul 22, 2021

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

@BonBonSlick
Copy link

BonBonSlick commented Feb 10, 2022

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

@dginovker
Copy link

dginovker commented Apr 26, 2022

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

@BonBonSlick
Copy link

Just a note to everyone else, I was using a lib, thats it. Because too many cases cant be covered by all snippets above

@anton000
Copy link

anton000 commented Nov 2, 2022

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

Added this definition to avoid using @ts-expect-error

interface WrapperState {
  _valueTracker? : {
    getValue(): string,
  setValue(value: string): void,
  stopTracking(): void,
  }
}
export type ElementWithValueTracker = HTMLInputElement & WrapperState;

inomdzhon added a commit to VKCOM/VKUI that referenced this issue Dec 6, 2023
h2. Описание

Немного отрефакторил.

- `Chip` перенеёс в `ChipInputBase`.
- Хук `useChipsInput` в `ChipsInput`.
- Хук `useChipsSelect` в `ChipsSelect`.
- Вынес шаренные типы в файлы `types`.
- Вынес шаренные константы типы в файлы `constants`.
- Теперь `ChipsInput` и `ChipsSelect` могут быть неконтролируемыми.
- В `ChipsSelect` параметры `creatable` и `emptyText` унёс в хук `useChipsSelect`.

<details><summary>Под катом изменения по `ChipsSelect`</summary>
<p>

h2. ChipsSelect

```diff
<ChipsSelect
-  value={[]}
+  defaultValue={[]}

-  value={[]}
+  value={[]}
+  onChange={[]}

-  inputValue=""
+  defaultInputValue=""

-  popupDirection="bottom"
+  placement="bottom"

-  options={[]}
+  presets={[]}

-  showSelected={true}
+  selectedBehavior="highlight"

-  showSelected={false}
+  selectedBehavior="hide"

-  creatableText="Lorem Ipsum"
+  creatable="Lorem Ipsum"
/>
```

- Компонент теперь может быть контролируемым и неконтролируемым.
- `creatable` – может быть всё ещё `boolean`, при этом теперь можно передать и текст, чтобы
  переопределить текст по умолчанию.
- `getOptionValue`, `getOptionLabel`, `getNewOptionData` – все аргументы функции теперь обязательны.
- `renderChip` – вторым аргументов приходит `option`.

</p>
</details> 

h3. Доступность

Обговорили с нашим специалистом по цифровой доступности следующие правила и поведение:

- Контейнеры `ChipsInput` и `ChipsSelect` имеют роль `listbox` и `aria-orientation="horizontal"`, а выбранные опции имеют роль `option`, а также атрибуты:
  - `aria-selected` со значением true;
  - `aria-posinset` со значением текущего места в списке;
  - `aria-setsize` со значением общего количества выбранных опций.
- В `ChipsSelect`:
  - поле ввода имеет роль `combobox` и ссылаться на всплывающее окно через `aria-haspopup`, `aria-controls` и `aria-expanded`
  - Всплывающее окно имеет роль `listbox`, а его опции роль `option`.
- Навигация с клавиатуры:
  - **Tab** фокусирует на поле ввода.
  - Если поле пустое, то стрелка **ArrowUp**/**ArrowLeft** выставит фокус на опцию с конца. Далее с помощью стрелок **ArrowUp**/**ArrowLeft** и **ArrowDown**/**ArrowRIght** можно перемещаться между опциями.
  - Нажатие **Tab** переведёт обратно на поле ввода, а **Shift + Tab** на другой фокусируемый элемент до `ChipsInput`/`ChipsSelect`. Другими словами опции не реагируют на **Tab**, на них можно сфокусироваться только кнопками-стрелками.

h2. Изменения

- `hooks/useEnsureControl` удалил `undefined` из возвращаемого типа. `value` не может быть `undefined`, потому что `useCustomEnsuredControl` возвращает `defaultValue`.
- `lib/select` немного отрефакторил, удалил легаси код связанный с флагом `u` у **RegExp**.
- `lib/appearance`
  - Добавил `Keys.BACKSPACE`.
  - Новая ф-я `getHorizontalFocusGoTo`.
- `lib/react/simulateReactInput` – так как теперь для `input` используется `useEnsureControl`, то нужна возможность очистить поле через событие. Т.к. у в DOM API нет события `clear`, вызываем событие через API в React (см. facebook/react#11488 (comment)).
- `components/Popper` – поправил проверку для вызова `onPlacementChange`  из #6185.
  - в `ChipsSelect` задаём значение по умолчанию для `placement`.
  - в `CustomSelectDropdown` задаём значение по умолчанию для `placement`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests