Skip to content

Commit f039e2e

Browse files
committed
Add dispatchEvent to fragment instances
1 parent 4f5bd2c commit f039e2e

File tree

4 files changed

+556
-369
lines changed

4 files changed

+556
-369
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import TestCase from '../../TestCase';
2+
import Fixture from '../../Fixture';
3+
4+
const React = window.React;
5+
const {Fragment, useRef, useState} = React;
6+
7+
function WrapperComponent(props) {
8+
return props.children;
9+
}
10+
11+
function handler(e) {
12+
const text = e.currentTarget.innerText;
13+
alert('You clicked: ' + text);
14+
}
15+
16+
const initialState = {
17+
child: false,
18+
parent: false,
19+
grandparent: false,
20+
};
21+
22+
export default function EventListenerCase() {
23+
const fragmentRef = useRef(null);
24+
const [clickedState, setClickedState] = useState({...initialState});
25+
26+
return (
27+
<TestCase title="Event Dispatch">
28+
<TestCase.Steps>
29+
<li>
30+
Each div has regular click handlers, you can click each one to observe
31+
the status changing
32+
</li>
33+
<li>Clear the clicked state</li>
34+
<li>
35+
Click the "Dispatch click event" button to dispatch a click event on
36+
the Fragment
37+
</li>
38+
</TestCase.Steps>
39+
40+
<TestCase.ExpectedResult>
41+
Dispatching an event on a Fragment will forward the dispatch to its
42+
parent. You can observe when dispatching that the parent handler is
43+
called in additional to bubbling from there. A delay is added to make
44+
the bubbling more clear.
45+
</TestCase.ExpectedResult>
46+
47+
<Fixture>
48+
<Fixture.Controls>
49+
<button
50+
onClick={() => {
51+
fragmentRef.current.dispatchEvent(
52+
new MouseEvent('click', {bubbles: true})
53+
);
54+
}}>
55+
Dispatch click event
56+
</button>
57+
<button
58+
onClick={() => {
59+
setClickedState({...initialState});
60+
}}>
61+
Reset clicked state
62+
</button>
63+
</Fixture.Controls>
64+
<div
65+
onClick={() => {
66+
setTimeout(() => {
67+
setClickedState(prev => ({...prev, grandparent: true}));
68+
}, 200);
69+
}}
70+
className="card">
71+
Fragment grandparent - clicked:{' '}
72+
{clickedState.grandparent ? 'true' : 'false'}
73+
<div
74+
onClick={() => {
75+
setTimeout(() => {
76+
setClickedState(prev => ({...prev, parent: true}));
77+
}, 100);
78+
}}
79+
className="card">
80+
Fragment parent - clicked: {clickedState.parent ? 'true' : 'false'}
81+
<Fragment ref={fragmentRef}>
82+
<div
83+
className="card"
84+
onClick={() => {
85+
setClickedState(prev => ({...prev, child: true}));
86+
}}>
87+
Fragment child - clicked:{' '}
88+
{clickedState.child ? 'true' : 'false'}
89+
</div>
90+
</Fragment>
91+
</div>
92+
</div>
93+
</Fixture>
94+
</TestCase>
95+
);
96+
}

fixtures/dom/src/components/fixtures/fragment-refs/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import FixtureSet from '../../FixtureSet';
22
import EventListenerCase from './EventListenerCase';
3+
import EventDispatchCase from './EventDispatchCase';
34
import IntersectionObserverCase from './IntersectionObserverCase';
45
import ResizeObserverCase from './ResizeObserverCase';
56
import FocusCase from './FocusCase';
@@ -11,6 +12,7 @@ export default function FragmentRefsPage() {
1112
return (
1213
<FixtureSet title="Fragment Refs">
1314
<EventListenerCase />
15+
<EventDispatchCase />
1416
<IntersectionObserverCase />
1517
<ResizeObserverCase />
1618
<FocusCase />

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2416,6 +2416,7 @@ export type FragmentInstanceType = {
24162416
listener: EventListener,
24172417
optionsOrUseCapture?: EventListenerOptionsOrUseCapture,
24182418
): void,
2419+
dispatchEvent(event: Event): boolean,
24192420
focus(focusOptions?: FocusOptions): void,
24202421
focusLast(focusOptions?: FocusOptions): void,
24212422
blur(): void,
@@ -2513,6 +2514,23 @@ function removeEventListenerFromChild(
25132514
return false;
25142515
}
25152516
// $FlowFixMe[prop-missing]
2517+
FragmentInstance.prototype.dispatchEvent = function (
2518+
this: FragmentInstanceType,
2519+
event: Event,
2520+
): boolean {
2521+
const parentHostInstance = getFragmentParentHostInstance(this._fragmentFiber);
2522+
if (parentHostInstance === null) {
2523+
if (__DEV__) {
2524+
console.error(
2525+
'You are attempting to dispatch an event on a disconnected ' +
2526+
'FragmentInstance. No event was dispatched.',
2527+
);
2528+
}
2529+
return true;
2530+
}
2531+
return parentHostInstance.dispatchEvent(event);
2532+
};
2533+
// $FlowFixMe[prop-missing]
25162534
FragmentInstance.prototype.focus = function (
25172535
this: FragmentInstanceType,
25182536
focusOptions?: FocusOptions,

0 commit comments

Comments
 (0)