ReactDOM.useEvent: wire to event system to the hook (#18304)

This commit is contained in:
Dominic Gannaway
2020-03-16 21:46:17 +00:00
committed by GitHub
parent 297f7588c4
commit c804f9aebb
12 changed files with 710 additions and 6 deletions
+9 -1
View File
@@ -79,12 +79,20 @@ export function executeDispatchesInOrder(event) {
validateEventDispatches(event);
}
if (Array.isArray(dispatchListeners)) {
let previousInstance;
for (let i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
const instance = dispatchInstances[i];
// We check if the instance was the same as the last one,
// if it was, then we're still on the same instance thus
// propagation should not stop. If we add support for
// stopImmediatePropagation at some point, then we'll
// need to handle that case here differently.
if (instance !== previousInstance && event.isPropagationStopped()) {
break;
}
// Listeners and Instances are two parallel arrays that are always in sync.
executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
previousInstance = instance;
}
} else if (dispatchListeners) {
executeDispatch(event, dispatchListeners, dispatchInstances);
+4
View File
@@ -470,6 +470,10 @@ export function beforeRemoveInstance(instance) {
// noop
}
export function registerEvent(event: any, rootContainerInstance: any) {
throw new Error('Not yet implemented.');
}
export function mountEventListener(listener: any) {
throw new Error('Not yet implemented.');
}
+37
View File
@@ -7,12 +7,14 @@
* @flow
*/
import type {DOMTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
import type {RootType} from './ReactDOMRoot';
import {
precacheFiberNode,
updateFiberProps,
getClosestInstanceFromNode,
getListenersFromTarget,
} from './ReactDOMComponentTree';
import {
createElement,
@@ -78,7 +80,9 @@ import {
detachElementListener,
isDOMDocument,
isDOMElement,
listenToTopLevelEvent,
} from '../events/DOMModernPluginEventSystem';
import {getListenerMapForElement} from '../events/DOMEventListenerMap';
export type ReactListenerEvent = ReactDOMListenerEvent;
export type ReactListenerMap = ReactDOMListenerMap;
@@ -529,6 +533,22 @@ export function beforeRemoveInstance(
) {
dispatchBeforeDetachedBlur(((instance: any): HTMLElement));
}
if (enableUseEventAPI) {
// It's unfortunate that we have to do this cleanup, but
// it's necessary otherwise we will leak the host instances
// from the useEvent hook instances Map. We call destroy
// on each listener to ensure we properly remove the instance
// from the instances Map. Note: we have this Map so that we
// can properly unmount instances when the function component
// that the hook is attached to gets unmounted.
const listenersSet = getListenersFromTarget(instance);
if (listenersSet !== null) {
const listeners = Array.from(listenersSet);
for (let i = 0; i < listeners.length; i++) {
listeners[i].destroy(instance);
}
}
}
}
export function removeChild(
@@ -1083,6 +1103,23 @@ export function getInstanceFromNode(node: HTMLElement): null | Object {
return getClosestInstanceFromNode(node) || null;
}
export function registerEvent(
event: ReactDOMListenerEvent,
rootContainerInstance: Container,
): void {
const {passive, priority, type} = event;
const listenerMap = getListenerMapForElement(rootContainerInstance);
// Add the event listener to the target container (falling back to
// the target if we didn't find one).
listenToTopLevelEvent(
((type: any): DOMTopLevelEventType),
rootContainerInstance,
listenerMap,
passive,
priority,
);
}
export function mountEventListener(listener: ReactDOMListener): void {
if (enableUseEventAPI) {
const {target} = listener;
+1 -1
View File
@@ -191,7 +191,7 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
nativeEvent,
nativeEventTarget,
);
accumulateTwoPhaseListeners(event);
accumulateTwoPhaseListeners(event, true);
return event;
},
};
@@ -1074,5 +1074,530 @@ describe('DOMModernPluginEventSystem', () => {
expect(listenerMaps.length).toEqual(2);
expect(listenerMaps[0]).toEqual(listenerMaps[1]);
});
it('can render correctly with the ReactDOMServer', () => {
const clickEvent = jest.fn();
function Test() {
const divRef = React.useRef(null);
const click = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click.setListener(divRef.current, clickEvent);
});
return <div ref={divRef}>Hello world</div>;
}
const output = ReactDOMServer.renderToString(<Test />);
expect(output).toBe(`<div data-reactroot="">Hello world</div>`);
});
it('can render correctly with the ReactDOMServer hydration', () => {
const clickEvent = jest.fn();
const spanRef = React.createRef();
function Test() {
const click = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click.setListener(spanRef.current, clickEvent);
});
return (
<div>
<span ref={spanRef}>Hello world</span>
</div>
);
}
const output = ReactDOMServer.renderToString(<Test />);
expect(output).toBe(
`<div data-reactroot=""><span>Hello world</span></div>`,
);
container.innerHTML = output;
ReactDOM.hydrate(<Test />, container);
Scheduler.unstable_flushAll();
dispatchClickEvent(spanRef.current);
expect(clickEvent).toHaveBeenCalledTimes(1);
});
it('should correctly work for a basic "click" listener', () => {
let log = [];
const clickEvent = jest.fn(event => {
log.push({
eventPhase: event.eventPhase,
type: event.type,
currentTarget: event.currentTarget,
target: event.target,
});
});
const divRef = React.createRef();
const buttonRef = React.createRef();
function Test() {
const click = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click.setListener(buttonRef.current, clickEvent);
});
return (
<button ref={buttonRef}>
<div ref={divRef}>Click me!</div>
</button>
);
}
ReactDOM.render(<Test />, container);
Scheduler.unstable_flushAll();
expect(container.innerHTML).toBe('<button><div>Click me!</div></button>');
// Clicking the button should trigger the event callback
let divElement = divRef.current;
dispatchClickEvent(divElement);
expect(log).toEqual([
{
eventPhase: 3,
type: 'click',
currentTarget: buttonRef.current,
target: divRef.current,
},
]);
expect(clickEvent).toBeCalledTimes(1);
// Unmounting the container and clicking should not work
ReactDOM.render(null, container);
Scheduler.unstable_flushAll();
dispatchClickEvent(divElement);
expect(clickEvent).toBeCalledTimes(1);
// Re-rendering the container and clicking should work
ReactDOM.render(<Test />, container);
Scheduler.unstable_flushAll();
divElement = divRef.current;
dispatchClickEvent(divElement);
expect(clickEvent).toBeCalledTimes(2);
log = [];
// Clicking the button should also work
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(log).toEqual([
{
eventPhase: 3,
type: 'click',
currentTarget: buttonRef.current,
target: buttonRef.current,
},
]);
function Test2({clickEvent2}) {
const click = ReactDOM.unstable_useEvent('click', clickEvent2);
React.useEffect(() => {
click.setListener(buttonRef.current, clickEvent2);
});
return (
<button ref={buttonRef}>
<div ref={divRef}>Click me!</div>
</button>
);
}
let clickEvent2 = jest.fn();
ReactDOM.render(<Test2 clickEvent2={clickEvent2} />, container);
Scheduler.unstable_flushAll();
divElement = divRef.current;
dispatchClickEvent(divElement);
expect(clickEvent2).toBeCalledTimes(1);
// Reset the function we pass in, so it's different
clickEvent2 = jest.fn();
ReactDOM.render(<Test2 clickEvent2={clickEvent2} />, container);
Scheduler.unstable_flushAll();
divElement = divRef.current;
dispatchClickEvent(divElement);
expect(clickEvent2).toBeCalledTimes(1);
});
it('should correctly work for setting and clearing a basic "click" listener', () => {
const clickEvent = jest.fn();
const divRef = React.createRef();
const buttonRef = React.createRef();
function Test({off}) {
const click = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click.setListener(buttonRef.current, clickEvent);
});
React.useEffect(() => {
if (off) {
click.setListener(buttonRef.current, null);
}
}, [off]);
return (
<button ref={buttonRef}>
<div ref={divRef}>Click me!</div>
</button>
);
}
ReactDOM.render(<Test off={false} />, container);
Scheduler.unstable_flushAll();
let divElement = divRef.current;
dispatchClickEvent(divElement);
expect(clickEvent).toBeCalledTimes(1);
// The listener should get unmounted in the second effect
ReactDOM.render(<Test off={true} />, container);
Scheduler.unstable_flushAll();
clickEvent.mockClear();
divElement = divRef.current;
dispatchClickEvent(divElement);
expect(clickEvent).toBeCalledTimes(0);
});
it('handle propagation of click events', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
const log = [];
const onClick = jest.fn(e => log.push(['bubble', e.currentTarget]));
const onClickCapture = jest.fn(e =>
log.push(['capture', e.currentTarget]),
);
function Test() {
const click = ReactDOM.unstable_useEvent('click');
const clickCapture = ReactDOM.unstable_useEvent('click', {
capture: true,
});
React.useEffect(() => {
click.setListener(buttonRef.current, onClick);
clickCapture.setListener(buttonRef.current, onClickCapture);
click.setListener(divRef.current, onClick);
clickCapture.setListener(divRef.current, onClickCapture);
});
return (
<button ref={buttonRef}>
<div ref={divRef}>Click me!</div>
</button>
);
}
ReactDOM.render(<Test />, container);
Scheduler.unstable_flushAll();
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(onClick).toHaveBeenCalledTimes(1);
expect(onClickCapture).toHaveBeenCalledTimes(1);
expect(log[0]).toEqual(['capture', buttonElement]);
expect(log[1]).toEqual(['bubble', buttonElement]);
let divElement = divRef.current;
dispatchClickEvent(divElement);
expect(onClick).toHaveBeenCalledTimes(3);
expect(onClickCapture).toHaveBeenCalledTimes(3);
expect(log[2]).toEqual(['capture', buttonElement]);
expect(log[3]).toEqual(['capture', divElement]);
expect(log[4]).toEqual(['bubble', divElement]);
expect(log[5]).toEqual(['bubble', buttonElement]);
});
it('should correctly work for a basic "click" listener on the outer target', () => {
const log = [];
const clickEvent = jest.fn(event => {
log.push({
eventPhase: event.eventPhase,
type: event.type,
currentTarget: event.currentTarget,
target: event.target,
});
});
const divRef = React.createRef();
const buttonRef = React.createRef();
function Test() {
const click = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click.setListener(divRef.current, clickEvent);
});
return (
<button ref={buttonRef}>
<div ref={divRef}>Click me!</div>
</button>
);
}
ReactDOM.render(<Test />, container);
Scheduler.unstable_flushAll();
expect(container.innerHTML).toBe('<button><div>Click me!</div></button>');
// Clicking the button should trigger the event callback
let divElement = divRef.current;
dispatchClickEvent(divElement);
expect(log).toEqual([
{
eventPhase: 3,
type: 'click',
currentTarget: divRef.current,
target: divRef.current,
},
]);
// Unmounting the container and clicking should not work
ReactDOM.render(null, container);
dispatchClickEvent(divElement);
expect(clickEvent).toBeCalledTimes(1);
// Re-rendering the container and clicking should work
ReactDOM.render(<Test />, container);
Scheduler.unstable_flushAll();
divElement = divRef.current;
dispatchClickEvent(divElement);
expect(clickEvent).toBeCalledTimes(2);
// Clicking the button should not work
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(clickEvent).toBeCalledTimes(2);
});
it('should correctly handle many nested target listeners', () => {
const buttonRef = React.createRef();
const targetListener1 = jest.fn();
const targetListener2 = jest.fn();
const targetListener3 = jest.fn();
const targetListener4 = jest.fn();
function Test() {
const click1 = ReactDOM.unstable_useEvent('click', {capture: true});
const click2 = ReactDOM.unstable_useEvent('click', {capture: true});
const click3 = ReactDOM.unstable_useEvent('click');
const click4 = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click1.setListener(buttonRef.current, targetListener1);
click2.setListener(buttonRef.current, targetListener2);
click3.setListener(buttonRef.current, targetListener3);
click4.setListener(buttonRef.current, targetListener4);
});
return <button ref={buttonRef}>Click me!</button>;
}
ReactDOM.render(<Test />, container);
Scheduler.unstable_flushAll();
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(targetListener1).toHaveBeenCalledTimes(1);
expect(targetListener2).toHaveBeenCalledTimes(1);
expect(targetListener3).toHaveBeenCalledTimes(1);
expect(targetListener4).toHaveBeenCalledTimes(1);
function Test2() {
const click1 = ReactDOM.unstable_useEvent('click');
const click2 = ReactDOM.unstable_useEvent('click');
const click3 = ReactDOM.unstable_useEvent('click');
const click4 = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click1.setListener(buttonRef.current, targetListener1);
click2.setListener(buttonRef.current, targetListener2);
click3.setListener(buttonRef.current, targetListener3);
click4.setListener(buttonRef.current, targetListener4);
});
return <button ref={buttonRef}>Click me!</button>;
}
ReactDOM.render(<Test2 />, container);
Scheduler.unstable_flushAll();
buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(targetListener1).toHaveBeenCalledTimes(2);
expect(targetListener2).toHaveBeenCalledTimes(2);
expect(targetListener3).toHaveBeenCalledTimes(2);
expect(targetListener4).toHaveBeenCalledTimes(2);
});
it('should correctly handle stopPropagation corrrectly for target events', () => {
const buttonRef = React.createRef();
const divRef = React.createRef();
let clickEvent = jest.fn();
function Test() {
const click1 = ReactDOM.unstable_useEvent('click', {
bind: buttonRef,
});
const click2 = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click1.setListener(buttonRef.current, clickEvent);
click2.setListener(divRef.current, e => {
e.stopPropagation();
});
});
return (
<button ref={buttonRef}>
<div ref={divRef}>Click me!</div>
</button>
);
}
ReactDOM.render(<Test />, container);
Scheduler.unstable_flushAll();
let divElement = divRef.current;
dispatchClickEvent(divElement);
expect(clickEvent).toHaveBeenCalledTimes(0);
});
it('should correctly handle stopPropagation corrrectly for many target events', () => {
const buttonRef = React.createRef();
const targetListerner1 = jest.fn(e => e.stopPropagation());
const targetListerner2 = jest.fn(e => e.stopPropagation());
const targetListerner3 = jest.fn(e => e.stopPropagation());
const targetListerner4 = jest.fn(e => e.stopPropagation());
function Test() {
const click1 = ReactDOM.unstable_useEvent('click');
const click2 = ReactDOM.unstable_useEvent('click');
const click3 = ReactDOM.unstable_useEvent('click');
const click4 = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click1.setListener(buttonRef.current, targetListerner1);
click2.setListener(buttonRef.current, targetListerner2);
click3.setListener(buttonRef.current, targetListerner3);
click4.setListener(buttonRef.current, targetListerner4);
});
return <button ref={buttonRef}>Click me!</button>;
}
ReactDOM.render(<Test />, container);
Scheduler.unstable_flushAll();
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(targetListerner1).toHaveBeenCalledTimes(1);
expect(targetListerner2).toHaveBeenCalledTimes(1);
expect(targetListerner3).toHaveBeenCalledTimes(1);
expect(targetListerner4).toHaveBeenCalledTimes(1);
});
it('should correctly handle stopPropagation for mixed capture/bubbling target listeners', () => {
const buttonRef = React.createRef();
const targetListerner1 = jest.fn(e => e.stopPropagation());
const targetListerner2 = jest.fn(e => e.stopPropagation());
const targetListerner3 = jest.fn(e => e.stopPropagation());
const targetListerner4 = jest.fn(e => e.stopPropagation());
function Test() {
const click1 = ReactDOM.unstable_useEvent('click', {capture: true});
const click2 = ReactDOM.unstable_useEvent('click', {capture: true});
const click3 = ReactDOM.unstable_useEvent('click');
const click4 = ReactDOM.unstable_useEvent('click');
React.useEffect(() => {
click1.setListener(buttonRef.current, targetListerner1);
click2.setListener(buttonRef.current, targetListerner2);
click3.setListener(buttonRef.current, targetListerner3);
click4.setListener(buttonRef.current, targetListerner4);
});
return <button ref={buttonRef}>Click me!</button>;
}
ReactDOM.render(<Test />, container);
Scheduler.unstable_flushAll();
let buttonElement = buttonRef.current;
dispatchClickEvent(buttonElement);
expect(targetListerner1).toHaveBeenCalledTimes(1);
expect(targetListerner2).toHaveBeenCalledTimes(1);
expect(targetListerner3).toHaveBeenCalledTimes(1);
expect(targetListerner4).toHaveBeenCalledTimes(1);
});
it.experimental('should work with concurrent mode updates', async () => {
const log = [];
const ref = React.createRef();
function Test({counter}) {
const click = ReactDOM.unstable_useEvent('click');
React.useLayoutEffect(() => {
click.setListener(ref.current, () => {
log.push({counter});
});
});
Scheduler.unstable_yieldValue('Test');
return <button ref={ref}>Press me</button>;
}
let root = ReactDOM.createRoot(container);
root.render(<Test counter={0} />);
// Dev double-render
if (__DEV__) {
expect(Scheduler).toFlushAndYield(['Test', 'Test']);
} else {
expect(Scheduler).toFlushAndYield(['Test']);
}
// Click the button
dispatchClickEvent(ref.current);
expect(log).toEqual([{counter: 0}]);
// Clear log
log.length = 0;
// Increase counter
root.render(<Test counter={1} />);
// Yield before committing
// Dev double-render
if (__DEV__) {
expect(Scheduler).toFlushAndYieldThrough(['Test', 'Test']);
} else {
expect(Scheduler).toFlushAndYieldThrough(['Test']);
}
// Click the button again
dispatchClickEvent(ref.current);
expect(log).toEqual([{counter: 0}]);
// Clear log
log.length = 0;
// Commit
expect(Scheduler).toFlushAndYield([]);
dispatchClickEvent(ref.current);
expect(log).toEqual([{counter: 1}]);
});
});
});
@@ -9,11 +9,15 @@
import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
import getListener from 'legacy-events/getListener';
import {HostComponent} from 'shared/ReactWorkTags';
import {enableUseEventAPI} from 'shared/ReactFeatureFlags';
import getListener from 'legacy-events/getListener';
import {getListenersFromTarget} from '../client/ReactDOMComponentTree';
export default function accumulateTwoPhaseListeners(
event: ReactSyntheticEvent,
accumulateUseEventListeners?: boolean,
): void {
const phasedRegistrationNames = event.dispatchConfig.phasedRegistrationNames;
if (phasedRegistrationNames == null) {
@@ -28,6 +32,33 @@ export default function accumulateTwoPhaseListeners(
while (node !== null) {
// We only care for listeners that are on HostComponents (i.e. <div>)
if (node.tag === HostComponent) {
// For useEvent listenrs
if (enableUseEventAPI && accumulateUseEventListeners) {
// useEvent event listeners
const instance = node.stateNode;
const targetType = event.type;
const listeners = getListenersFromTarget(instance);
if (listeners !== null) {
const listenersArr = Array.from(listeners);
for (let i = 0; i < listenersArr.length; i++) {
const listener = listenersArr[i];
const {
callback,
event: {capture, type},
} = listener;
if (type === targetType) {
if (capture === true) {
dispatchListeners.unshift(callback);
dispatchInstances.unshift(node);
} else {
dispatchListeners.push(callback);
dispatchInstances.push(node);
}
}
}
}
}
// Standard React on* listeners, i.e. onClick prop
const captureListener = getListener(node, captured);
if (captureListener != null) {
@@ -474,6 +474,10 @@ export function beforeRemoveInstance(instance: any) {
// noop
}
export function registerEvent(event: any, rootContainerInstance: Container) {
throw new Error('Not yet implemented.');
}
export function mountEventListener(listener: any) {
throw new Error('Not yet implemented.');
}
@@ -518,6 +518,10 @@ export function beforeRemoveInstance(instance: any) {
// noop
}
export function registerEvent(event: any, rootContainerInstance: Container) {
throw new Error('Not yet implemented.');
}
export function mountEventListener(listener: any) {
throw new Error('Not yet implemented.');
}
+3
View File
@@ -36,6 +36,7 @@ import {
enableSuspenseCallback,
enableScopeAPI,
runAllPassiveEffectDestroysBeforeCreates,
enableUseEventAPI,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
@@ -1053,6 +1054,8 @@ function commitUnmount(
case HostComponent: {
if (enableDeprecatedFlareAPI) {
unmountDeprecatedResponderListeners(current);
}
if (enableDeprecatedFlareAPI || enableUseEventAPI) {
beforeRemoveInstance(current.stateNode);
}
safelyDetachRef(current);
+86 -3
View File
@@ -24,6 +24,7 @@ import type {FiberRoot} from './ReactFiberRoot';
import type {
ReactListenerEvent,
ReactListenerMap,
ReactListener,
} from './ReactFiberHostConfig';
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -53,6 +54,12 @@ import {
markRenderEventTimeAndConfig,
markUnprocessedUpdateTime,
} from './ReactFiberWorkLoop';
import {
registerEvent,
mountEventListener as mountHostEventListener,
unmountEventListener as unmountHostEventListener,
validateEventListenerTarget,
} from './ReactFiberHostConfig';
import invariant from 'shared/invariant';
import getComponentName from 'shared/getComponentName';
@@ -73,6 +80,7 @@ import {
setWorkInProgressVersion,
warnAboutMultipleRenderersDEV,
} from './ReactMutableSource';
import {getRootHostContainer} from './ReactFiberHostContext';
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
@@ -1627,18 +1635,93 @@ function dispatchAction<S, A>(
const noOpMount = () => {};
function validateNotInFunctionRender(): boolean {
if (currentlyRenderingFiber === null) {
return true;
}
if (__DEV__) {
console.warn(
'Event listener methods from useEvent() cannot be called during render.' +
' These methods should be called in an effect or event callback outside the render.',
);
}
return false;
}
function createReactListener(
event: ReactListenerEvent,
callback: Event => void,
target: EventTarget,
destroy: Node => void,
): ReactListener {
return {
callback,
destroy,
event,
target,
};
}
function mountEventListener(event: ReactListenerEvent): ReactListenerMap {
if (enableUseEventAPI) {
const hook = mountWorkInProgressHook();
const listenerMap: Map<EventTarget, ReactListener> = new Map();
const rootContainerInstance = getRootHostContainer();
// Register the event to the current root to ensure event
// replaying can pick up the event ahead of time.
registerEvent(event, rootContainerInstance);
const clear = () => {
// TODO
if (validateNotInFunctionRender()) {
const listeners = Array.from(listenerMap.values());
for (let i = 0; i < listeners.length; i++) {
unmountHostEventListener(listeners[i]);
}
listenerMap.clear();
}
};
const destroy = (target: Node) => {
// We don't need to call detachListenerFromInstance
// here as this method should only ever be called
// from renderers that need to remove the instance
// from the map representing an instance that still
// holds a reference to the listenerMap. This means
// things like "window" listeners on ReactDOM should
// never enter this call path as the the instance in
// those cases would be that of "window", which
// should be handled via an optimized route in the
// renderer, making less overhead here. If we change
// this heuristic we should update this path to make
// sure we call detachListenerFromInstance.
listenerMap.delete(target);
};
const reactListenerMap: ReactListenerMap = {
clear,
setListener(instance: EventTarget, callback: ?(Event) => void): void {
// TODO
setListener(target: EventTarget, callback: ?(Event) => void): void {
if (
validateNotInFunctionRender() &&
validateEventListenerTarget(target, callback)
) {
let listener = listenerMap.get(target);
if (listener === undefined) {
if (callback == null) {
return;
}
listener = createReactListener(event, callback, target, destroy);
listenerMap.set(target, listener);
} else {
if (callback == null) {
listenerMap.delete(target);
unmountHostEventListener(listener);
return;
}
listener.callback = callback;
}
mountHostEventListener(listener);
}
},
};
// In order to clear up upon the hook unmounting,
@@ -76,6 +76,7 @@ export const shouldUpdateFundamentalComponent =
$$$hostConfig.shouldUpdateFundamentalComponent;
export const getInstanceFromNode = $$$hostConfig.getInstanceFromNode;
export const beforeRemoveInstance = $$$hostConfig.beforeRemoveInstance;
export const registerEvent = $$$hostConfig.registerEvent;
export const mountEventListener = $$$hostConfig.mountEventListener;
export const unmountEventListener = $$$hostConfig.unmountEventListener;
export const validateEventListenerTarget =
@@ -380,6 +380,10 @@ export function beforeRemoveInstance(instance: any) {
// noop
}
export function registerEvent(event: any, rootContainerInstance: Container) {
throw new Error('Not yet implemented.');
}
export function mountEventListener(listener: any) {
throw new Error('Not yet implemented.');
}