@samitouri / QOS-React-2 / commits / 20f3546963

[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 });