简体   繁体   中英

Hidden input doesn't trigger React's onChange event

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?

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

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.

I think you might be approaching this the wrong way. When you click the button you want the state to update and that new state reflected in the hidden field. Instead of checking the change in the hidden field you should be checking the change in the state which you can do very easily with useEffect . That way you can do away with your refs and not over-complicate things.

function App() {
  const [fieldValue, setFieldValue] = useState(0);

  function handleClick() {
    setFieldValue(prev => prev + 1);
  }

  useEffect(() => {
     console.log('React:changed');
  }, [fieldValue]);

  return (
    <>
      <input type="hidden" value={fieldValue} />
      <button type="button" onClick={handleClick}>
        Hit it
      </button>
    </>
  );
}

Sandbox

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM