I'm facing a weird issue with the input field in React. I'm aware of the fact that hidden
input do not trigger input
nor change
events. However, even if I trigger them manually, React's onChange
event is still not being invoked.
I trigger both, change
and input
event here because React's onChange
is in fact the input
event. When I setup an event listener on the inputRef
(addEventListener("change", () => { ... })
) for testing purposes, it's being called without without problems. However, it turns out React is having some issue intercepting it.
Here is my current code:
const [fieldValue, setFieldValue] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = useCallback((): void => {
if (inputRef.current) {
inputRef.current.dispatchEvent(new Event("change", { bubbles: true }));
inputRef.current.dispatchEvent(new Event("input", { bubbles: true }));
}
setFieldValue(prev => prev + 1);
}, []);
JSX:
<input type="hidden" ref={inputRef} value={fieldValue} onChange={(e) => { console.log("React:onChange"); }} />
<button type="button" onClick={handleClick}>Hit it</button>
Am I doing anything wrong here? What else do I have to do to properly trigger React's onChange
event?
Events in React are handled by react-dom, through different plugins. Input events are managed with the SimpleEventPlugin (see https://github.com/facebook/react/blob/master/packages/react-dom/src/events/SimpleEventPlugin.js)
This plugin gets an event and redispatches it without interfering too much. So you can dispatch it as a native event, and it'll be triggered as a SyntheticEvent without much changes.
Change event is handled by the ChangeEventPlugin (see https://github.com/facebook/react/blob/master/packages/react-dom/src/events/ChangeEventPlugin.js). This plugins has this purpose:
This plugin creates an
onChange
event that normalizes change events across form elements. This event fires at a time when it's possible to change the element's value without seeing a flicker.
The plugin triggers change event but not exactly as the native change event. The native change event on text input element for example is triggered only on blur. See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event
For some elements, including , the change event doesn't fire until the control loses focus.
But with React, it's triggered differently, on every change of value. To do this, it is handled by the plugin which has these restrictions: The event will be triggered only on these input types:
color: true,
date: true,
datetime: true,
'datetime-local': true,
email: true,
month: true,
number: true,
password: true,
range: true,
search: true,
tel: true,
text: true,
time: true,
url: true,
week: true
So on hidden inputs, react stops the dispatch.
Another restriction is that the event will be dispatched only if the value of the input actually changes. See
if (updateValueIfChanged(targetNode)) {
return targetInst;
}
So even on a supported input, your dispatch won't go through the plugin. You can see it in this snippet, by manipulating a method that is used to get the value of the input you can manage to dispatch the event.
class Hello extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
//_valueTracker.getValue is used for comparing the values of the input. If you write fake value in the input it'll dispatch, anything else won't
document.getElementsByTagName('INPUT')[0]._valueTracker.getValue = () => {
return 'fake value'
}
}
render() {
const handleClick = () => {
if (this.myRef) {
this.myRef.current.dispatchEvent(new Event("change", {
bubbles: true
}));
}
};
return <div > < input type = "text"
ref = {
this.myRef
}
onChange = {
(e) => {
console.log("React:onChange");
}
}
/><button type="button" onClick={handleClick}>Hit it</button > < /div>
}
}
ReactDOM.render( <
Hello name = "World" / > ,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container">
</div>
So for a hidden input, there's no way to make it work (note that it is in line with the standard). For a text (or other supported inputs), you can but you need to more or less hack the method comparing the value before the event with the value after the event.
The onChange event is fired when the user alters the element value - in your case, you are programmatically changing the input value and hence onChange
is not firing up
onInput
events fires when value changes - regardless how. Therefore you need to add onInput
even listener instead of onChange
.
<input type="hidden" ref={inputRef} value={fieldValue} onInput={(e) => { console.log("React:onChange"); }} />
Here is a working example: Sandbox
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With