[Flare] Ensure Press event hook does not execute side-effects (#15976)
Dominic Gannaway committed
Jun 24, 2019 at 18:31 UTC
20f3546963c60c0030881049be0222a05601fa31
2 files changed
+129
-9
packages/react-events/src/Press.js
+53
-9
@@ -14,6 +14,7 @@ import type {
14
PointerType,
15
} from 'shared/ReactDOMTypes';
16
import type {EventPriority} from 'shared/ReactTypes';
17
+import warning from 'shared/warning';
18
19
import React from 'react';
20
import {DiscreteEvent, UserBlockingEvent} from 'shared/ReactTypes';
@@ -623,6 +624,24 @@ function updateIsPressWithinResponderRegion(
624
state.isPressWithinResponderRegion = isPressWithinResponderRegion;
625
}
626
627
+function handleStopPropagation(
628
+ props: PressProps,
629
+ context: ReactDOMResponderContext,
630
+ nativeEvent,
631
+): void {
632
+ const stopPropagation = props.stopPropagation;
633
+ if (stopPropagation !== undefined && context.isRespondingToHook()) {
634
+ if (__DEV__) {
635
+ warning(
636
+ false,
637
+ '"stopPropagation" prop cannot be passed to Press event hooks. This will result in a no-op.',
638
+ );
639
+ }
640
+ } else if (stopPropagation === true) {
641
+ nativeEvent.stopPropagation();
642
+ }
643
+}
644
+
645
const PressResponder: ReactDOMEventResponder = {
646
displayName: 'Press',
647
targetEventTypes,
@@ -667,9 +686,7 @@ const PressResponder: ReactDOMEventResponder = {
686
const nativeEvent: any = event.nativeEvent;
687
const isPressed = state.isPressed;
688
670
- if (props.stopPropagation === true) {
671
- nativeEvent.stopPropagation();
672
- }
689
+ handleStopPropagation(props, context, nativeEvent);
690
switch (type) {
691
// START
692
case 'pointerdown':
@@ -745,13 +762,34 @@ const PressResponder: ReactDOMEventResponder = {
762
}
763
764
case 'contextmenu': {
748
- if (props.preventContextMenu) {
765
+ const preventContextMenu = props.preventContextMenu;
766
+
767
+ if (preventContextMenu !== undefined && context.isRespondingToHook()) {
768
+ if (__DEV__) {
769
+ warning(
770
+ false,
771
+ '"preventContextMenu" prop cannot be passed to Press event hooks. This will result in a no-op.',
772
+ );
773
+ }
774
+ } else if (preventContextMenu === true) {
775
// Skip dispatching of onContextMenu below
776
nativeEvent.preventDefault();
777
}
778
779
if (isPressed) {
754
- if (props.preventDefault !== false && !nativeEvent.defaultPrevented) {
780
+ const preventDefault = props.preventDefault;
781
+
782
+ if (preventDefault !== undefined && context.isRespondingToHook()) {
783
+ if (__DEV__) {
784
+ warning(
785
+ false,
786
+ '"preventDefault" prop cannot be passed to Press event hooks. This will result in a no-op.',
787
+ );
788
+ }
789
+ } else if (
790
+ preventDefault !== false &&
791
+ !nativeEvent.defaultPrevented
792
+ ) {
793
// Skip dispatching of onContextMenu below
794
nativeEvent.preventDefault();
795
return;
@@ -787,9 +825,7 @@ const PressResponder: ReactDOMEventResponder = {
825
const isPressed = state.isPressed;
826
const activePointerId = state.activePointerId;
827
790
- if (props.stopPropagation === true) {
791
- nativeEvent.stopPropagation();
792
- }
828
+ handleStopPropagation(props, context, nativeEvent);
829
switch (type) {
830
// MOVE
831
case 'pointermove':
@@ -894,7 +930,15 @@ const PressResponder: ReactDOMEventResponder = {
930
} = (nativeEvent: MouseEvent);
931
// Check "open in new window/tab" and "open context menu" key modifiers
932
const preventDefault = props.preventDefault;
897
- if (
933
+
934
+ if (preventDefault !== undefined && context.isRespondingToHook()) {
935
+ if (__DEV__) {
936
+ warning(
937
+ false,
938
+ '"preventDefault" prop cannot be passed to Press event hooks. This will result in a no-op.',
939
+ );
940
+ }
941
+ } else if (
942
preventDefault !== false &&
943
!shiftKey &&
944
!metaKey &&
packages/react-events/src/__tests__/Press-test.internal.js
+76
@@ -2365,6 +2365,31 @@ describe('Event responder: Press', () => {
2365
expect.objectContaining({defaultPrevented: false}),
2366
);
2367
});
2368
+
2369
+ it('warns when preventDefault is used in an event hook', () => {
2370
+ const onPress = jest.fn();
2371
+ const preventDefault = jest.fn();
2372
+ const ref = React.createRef();
2373
+ const Component = () => {
2374
+ React.unstable_useEvent(Press, {preventDefault: false});
2375
+
2376
+ return (
2377
+ <Press onPress={onPress}>
2378
+ <a href="#" ref={ref} />
2379
+ </Press>
2380
+ );
2381
+ };
2382
+ ReactDOM.render(<Component />, container);
2383
+
2384
+ expect(() => {
2385
+ ref.current.dispatchEvent(createEvent('pointerdown'));
2386
+ ref.current.dispatchEvent(createEvent('pointerup'));
2387
+ ref.current.dispatchEvent(createEvent('click', {preventDefault}));
2388
+ }).toWarnDev(
2389
+ '"preventDefault" prop cannot be passed to Press event hooks. This will result in a no-op.',
2390
+ {withoutStack: true},
2391
+ );
2392
+ });
2393
});
2394
2395
describe('responder cancellation', () => {
@@ -2922,6 +2947,30 @@ describe('Event responder: Press', () => {
2947
});
2948
});
2949
2950
+ it('warns when preventContextMenu is used in an event hook', () => {
2951
+ const ref = React.createRef();
2952
+ const Component = () => {
2953
+ React.unstable_useEvent(Press, {preventContextMenu: false});
2954
+
2955
+ return (
2956
+ <Press preventContextMenu={true}>
2957
+ <div ref={ref} />
2958
+ </Press>
2959
+ );
2960
+ };
2961
+ ReactDOM.render(<Component />, container);
2962
+
2963
+ expect(() => {
2964
+ ref.current.dispatchEvent(
2965
+ createEvent('pointerdown', {pointerType: 'mouse', button: 2}),
2966
+ );
2967
+ ref.current.dispatchEvent(createEvent('contextmenu'));
2968
+ }).toWarnDev(
2969
+ '"preventContextMenu" prop cannot be passed to Press event hooks. This will result in a no-op.',
2970
+ {withoutStack: true},
2971
+ );
2972
+ });
2973
+
2974
it('should work correctly with stopPropagation set to true', () => {
2975
const ref = React.createRef();
2976
const element = (
@@ -2939,4 +2988,31 @@ describe('Event responder: Press', () => {
2988
container.removeEventListener('pointerdown', pointerDownEvent);
2989
expect(pointerDownEvent).toHaveBeenCalledTimes(0);
2990
});
2991
+
2992
+ it('warns when stopPropagation is used in an event hook', () => {
2993
+ const ref = React.createRef();
2994
+ const Component = () => {
2995
+ React.unstable_useEvent(Press, {stopPropagation: false});
2996
+
2997
+ return (
2998
+ <Press stopPropagation={true}>
2999
+ <a href="#" ref={ref} />
3000
+ </Press>
3001
+ );
3002
+ };
3003
+ const pointerDownEvent = jest.fn();
3004
+ container.addEventListener('pointerdown', pointerDownEvent);
3005
+ ReactDOM.render(<Component />, container);
3006
+
3007
+ expect(() => {
3008
+ ref.current.dispatchEvent(
3009
+ createEvent('pointerdown', {pointerType: 'mouse', button: 0}),
3010
+ );
3011
+ }).toWarnDev(
3012
+ '"stopPropagation" prop cannot be passed to Press event hooks. This will result in a no-op.',
3013
+ {withoutStack: true},
3014
+ );
3015
+ container.removeEventListener('pointerdown', pointerDownEvent);
3016
+ expect(pointerDownEvent).toHaveBeenCalledTimes(0);
3017
+ });
3018
});