javascriptreactjsdom-eventscustom-events

onChange prop and custom Event dispatch in React


I'm testing new version of my library tagger where there is custom Event dispatch on input element:

this._input.dispatchEvent(new Event('change', { bubbles: true }));

The problem is that the onChange prop on that input is not triggered when the custom event is triggered.

const App = () => {
    const [tags, setTags] = useState(null)
    const inputRef = useRef(null)

    // Write the Tagger code inside a useEffect hook
    // It will run when the component is initially rendered
    useEffect(() => {
        // Define the Tagger options
        const taggerOptions = {
            allow_spaces: true,
        }

        inputRef.current.addEventListener('change', (e) => {
            console.log(e);
        });

        // Initialize Tagger
        tagger(inputRef.current, taggerOptions)
    }, []);

    const onChange = (e) => {
        setTags(e.target.value)
    };

    return (
        <div className="app">
            <input type="text" ref={inputRef} onChange={onChange} defaultValue="charles, louis, michel" />

            {tags && <pre>{tags}</pre>}
        </div>
    )
}

On the above code, console.log is executed but setTags is not.

Is there a way to make custom change events on input be triggered on default event onChange={}?

If this is not possible I'm fine with an explanation why.

Is this documented somewhere that only native events can be used?

This is a CodePen demo where this can be tested.

EDIT

The supposed solution in What is the best way to trigger change or input event in react js, doesn't work for me. the problem with that question is not that the value is not updated properly, but the event is not triggered at all, even with the wrong value.

I've added this code to the library (sorry for the ES5 code, the library was not updated to modern JavaScript and I don't see a reason to migrate. The code works fine):

_update_input: function () {
    // ReactJS overwrite value setting on inputs, this is a workaround
    // ref: https://stackoverflow.com/a/46012210/387194
    var inputProto = window.HTMLInputElement.prototype;
    var nativeInputValueSetter = Object.getOwnPropertyDescriptor(inputProto, "value").set;
    nativeInputValueSetter.call(this._input, this._tags.join(','));
    this._input.dispatchEvent(new Event('input', { bubbles: true }));
},

Here is my experimental CodePen where this suppose to work but it doesn't. What did I do wrong? There has to be a simple explanation because the CodePen demo with the original solution works fine.

I even added this code and it still doesn't work (the event is not triggered):

const App = () => {
    const [value, setValue] = useState('charles, louis, michel');
    const inputRef = useRef(null);
    const tags = tags_array(value);

    useEffect(() => {
        const taggerOptions = {
            allow_spaces: true,
        };
        tagger(inputRef.current, taggerOptions);
    }, [inputRef]);

    const onChange = (event) => {
        setValue(event.target.value);
    };
    
    const trigger = () => {
        const input = inputRef.current;
        var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
        nativeInputValueSetter.call(input, 'one, two, three');
        input.dispatchEvent(new Event('input', { bubbles: true }));
    };

    return (
        <div className="app">
            <input type="text" value={value} ref={inputRef} onChange={onChange} />
            <br/>
            <ul>
                {tags.map((tag, index) => <li key={`${tag}-${index}`}>{tag}</li>)}
            </ul>
            <button onClick={trigger}>trigger change event</button>
        </div>
    )
}

To be sure that I've the same code, I modified CodePen from the original solution with React 18 and hooks and that code does work, even if the trigger of the event is inside React like in my code.


Solution

  • I've found what was the issue, I was checking my input element and it seems that if the input has type="hidden" the event is not triggered in ReactJS (or in the browser, I'm not sure).