From ccc473fb3cb4a3da3600ce8d0d8cbda471849887 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 15 Mar 2023 13:05:47 -0700 Subject: [PATCH 1/3] Create 0000-eventtarget-prop.md --- text/0000-eventtarget-prop.md | 155 ++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 text/0000-eventtarget-prop.md diff --git a/text/0000-eventtarget-prop.md b/text/0000-eventtarget-prop.md new file mode 100644 index 00000000..2574cd65 --- /dev/null +++ b/text/0000-eventtarget-prop.md @@ -0,0 +1,155 @@ +- Start Date: 2023-03-15 +- RFC PR: +- React Issue: + +# Summary + +This adds a `target` prop to every React component and a `useEventTarget` hook to use it. + +# Basic example + +```jsx +function ParentComponent() { + const [text, setText] = useState("Count: 0"); + const target = useEventTarget(); + target.addEventListener("count", (evt) => { + setText(`Count: ${evt.detail.count}`); + }); + + return ( + +

{text}

+
+ ); +} + +function ChildComponent({ children, target }) { + const [count, setCount] = useState(0); + const onClick = (count) => { + setCount(count); + target.dispatchEvent(new CustomEvent("count", { + detail: { count } + })); + }; + + return ( + <> + {children} + + + + ); +} +``` + +# Motivation + +The web is event-driven; it's time to make React be as well. + +The vast majority of Web APIs involve EventTarget, including DOM elements. +It's a simple yet elegant way of sharing data without disrupting the top-down nature of the DOM. +The dispatcher has no knowledge or control over who is listening and what will be done. +The child component's only role is to signal that new information is available. + +Parent-child communication without tightly coupling the two is a persistant problem in React. +Most existing solutions involve passing both the value and set function from `useState()` to the child. +Large-scale applications will often use Redux selectors and actions that operate on the same store in both parent and child. + + + +# Detailed design + +There are two elements to this proposal: making a `target` prop available to every component the way `children` is now, and a new `useEventListener()` hook to use it. + +`target` should be defined and available to every React component, regardless of whether or not the parent component has set the `target` prop. +With this change, every React component can be (mentally) thought of as the functional equivalent of a class inheriting EventTarget: +A component always has it's own `addEventListener` and `dispatchEvent` available to it. It never has to check to make sure they were given by a parent, and there are no changes to the behavior if there are no listeners. + +To listen to child events, a higher-level component must use the `useEventTarget` hook. +Components call `addEventListener` to listen to events, then pass it to a component through the `target` prop. + +Setting `target` should be considered equivalent to the following: `for (const [name, fn] of target) child.addEventListener(name, fn)` +To implement this, the JSX transformer will need to create an EventTarget instance, possibly using `useMemo`. +It will then need to connect the events from the `target` prop to the newly-created EventTarget instance, possibly using code like the example above. +The new EventTarget is then passed down as the `target` prop to the component along with the `children` prop. + + + +# Drawbacks + +The first drawback that comes to mind is that if a component already has a `target` property, this will almost certainly break backwards-compatibility with it. + +Another drawback is that since the `target` prop must always be available to any component it requires that every transformed component must keep an EventTarget instance under the hood. + + + + +# Alternatives + +- Do nothing. React works and there are user-land solutions for almost any problem. +- I briefly considered the [useEvent](https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md) hook proposed by @gaearon before realizing it was for different use-cases. +- My previous Signals and Slots RFC ([0000-signals-and-slots.md](https://github.com/Symbitic/rfcs/blob/master/text/0000-signals-and-slots.md)) was intended to solve similar problems, but it applies an outside concept instead if using the EventTarget already adopted by the web. +- User-land solutions like creating a new EventTarget and passing it. The problem is that this makes following the web optional rather than something built into React. +- Use `eventTarget` as the prop name instead of `target` as it would be less likely to already be in use. + + + +# Adoption strategy + +Once implemented + + + +# How we teach this + +The biggest problem with teaching is that most web developers will know that EventTarget is a class, which may lead them to think that using this requires switching back to class-based components. + +One way to solve this is to teach the `target` prop first, the `useEventTarget` hook second. + + + +# Unresolved questions + + From c026ed0b898c93954e3576e803e4585543c3b2ee Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 15 Mar 2023 13:10:25 -0700 Subject: [PATCH 2/3] Update 0000-eventtarget-prop.md --- text/0000-eventtarget-prop.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/text/0000-eventtarget-prop.md b/text/0000-eventtarget-prop.md index 2574cd65..d8ab7ee8 100644 --- a/text/0000-eventtarget-prop.md +++ b/text/0000-eventtarget-prop.md @@ -76,7 +76,7 @@ A component always has it's own `addEventListener` and `dispatchEvent` available To listen to child events, a higher-level component must use the `useEventTarget` hook. Components call `addEventListener` to listen to events, then pass it to a component through the `target` prop. -Setting `target` should be considered equivalent to the following: `for (const [name, fn] of target) child.addEventListener(name, fn)` +Setting `target` should be considered equivalent to the following: `for (const [name, fn] of target) child.addEventListener(name, fn)`. To implement this, the JSX transformer will need to create an EventTarget instance, possibly using `useMemo`. It will then need to connect the events from the `target` prop to the newly-created EventTarget instance, possibly using code like the example above. The new EventTarget is then passed down as the `target` prop to the component along with the `children` prop. @@ -93,7 +93,8 @@ defined here. The first drawback that comes to mind is that if a component already has a `target` property, this will almost certainly break backwards-compatibility with it. -Another drawback is that since the `target` prop must always be available to any component it requires that every transformed component must keep an EventTarget instance under the hood. +Another drawback is that since the `target` prop must always be available to any component it requires that every transformed component must keep an EventTarget instance under the hood even if it isn't being used. +I don't know much about the internals of the current version of React, so I can't say if there will be a performance penalty for this -# Adoption strategy - -Once implemented - - - # How we teach this -The biggest problem with teaching is that most web developers will know that EventTarget is a class, which may lead them to think that using this requires switching back to class-based components. +Our goal is to make React components more web-like. The word "more" should be emphasized to convey that this is not a breaking change: it simply helps React components act more like regular DOM elements. -One way to solve this is to teach the `target` prop first, the `useEventTarget` hook second. +In addition to documenting the `useEventTarget` hook, the three related props (`addEventListener`, `dispatchEvent`, and `eventTarget`) must also be taught. Since the `children` prop isn't especially well-documented, it might be useful to create a new page "Child Props" that describes every built-in prop available to all components.