Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 617
RA. Avatar asked Sep 15 '19 10:09

RA.


2 Answers

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.

like image 179
Julien Grégoire Avatar answered Oct 03 '22 20:10

Julien Grégoire


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

like image 22
Matthew Barbara Avatar answered Oct 03 '22 22:10

Matthew Barbara