react: Bug: manually created 'change' events via new Event() don't trigger React event handlers
Description:
Manually created events created via new Event and emitted from a hidden input work great for 'input' events, they bubble as expected and can be caught via onInput handlers, but using 'change' events this way doesn’t work – onChange handlers are never called.
The vanilla JS 'change' events do bubble normally and can be caught by parents with vanilla JS listeners ( using addEventListener), but the React onChange listeners don’t register anything.
I’ve created a codepen to demonstrate a minimal case for this via console logging. See repro steps below and code comments for additional details.
React version: 16.13.1
Steps To Reproduce
- Open https://codepen.io/jesseko/pen/dyMqGKG
- Observe that logging shows a single entry, the
changeevent being emitted from a child component after render. We expect a second log from a parent’sonChangebut it never comes. - Change
EVENT_TYPEto'input' - Observe that logging shows two entries, one for the event being emitted and a second for it being detected via an
onInputhandler in the parent component. - optional: there’s some commented out code at the bottom to test a vanilla JS listener. Change
EVENT_TYPEback to'change'and uncomment that code and you’ll see that that listener does work
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 3
- Comments: 15 (2 by maintainers)
This is also useful for using native web components which can fire a
changeevent when their value changes.It is surprising that React is not listening to
changeevents inside anonChangehandler.This unfortunate implementation detail of react enforces developers to use a
Refand manual code/bind to keep web components updated with react’s life cycle.@gaearon Are there any instructions on how to properly create synthetic event based on change event of input element?
I would also like to be able to do this. I am building an AngularJS-in-React bridge, and I’m mapping Angular’s concept of two-way binding to React change events. This allows React to see each bridged Angular component as something akin to a
<textarea />and the calling React component can then capture its state and treat it as a controlled component.I recognize that the very idea of this is unpleasant, but it is helping my team refactor our legacy AngularJS components into React from the top down. I’m also using an
onChangeprop to make this happen, and I don’t strictly need event bubbling, but it would make the abstraction less leaky to have a way of emitting change events from React components.Here’s an illustration of that scenario of logging
(page, section, component, action)Say you’re logging
("myPage", "articlesSection", "favoriteButton", "click")Using event bubbling you can make a global setup where you handle sections like
<TrackingSection sectionName="articlesSection">…content…and deep down in there, some <FavoriteButton> component could do
then TrackingSection could have it’s own handlers where it does
and then we log the event when it hits the top of the page if the event has all the expected parts.
If the section needs to discern between events, only log some and not others, or attach extra info for some, that can all happen right there and everything further down the tree can be agnostic about where they’re used - don’t have to thread extra props through or read from Context or import some section-specific tracking util.
It’s a really lightweight way of letting a button or input component say, “this happened, if you care” and letting a section say “I do care, thanks”. And if you need to modify the tracking at more places in the tree that’s easy too.
We do use
onChangeprops as well, but we still find that event bubbling is useful for a couple reasons:Most common usage is for instrumentation: you can listen to bubbled events from anywhere in the tree, which makes it easy to do things like track whether users interact with a certain section of the page (can fire an analytics event only for the first interaction in that section).
An advanced use of this: modify events intended for logging as they travel up the tree to gather context so you can log
(page, section, component, action)without needing any knowledge of section from the input where an event originates. i.e. the section component could catch the event and add a propertye.logging.section = "mySectionName".Centralizing handling: Our custom
Formcomponent is the thing that knows how to fire actions to the store and route them to the right form there. Input components emit, then the form catches the bubbled events and fires actions using props the form has. Again, saves us from having to pass that info down.If we were starting from scratch we could use Context to accomplish a lot of this, but I’m not convinced it’s a better path.
-If you want to keep the ability to modify an event more locally before it reaches someplace higher up, or just generally want the ability to get callbacks further up the tree, you end up needing to implement something that looks a whole lot like bubbling, and in that case why not just use the bubbling that’s built in. -If you don’t need that, Context could solve it well - just use it to pass down some bound functions or all the raw info needed for an input component to directly fire analytics events or actions to stores.
In our case, switching implementations would be expensive and risky, so just trying to continue to make it cleaner and easier to use without a drastic change.