From aedfcb6560f7020246bb037df21b62b86b851ee2 Mon Sep 17 00:00:00 2001
From: Jyrno Ader <jyrno42@gmail.com>
Date: Sat, 30 Mar 2019 21:16:33 +0200
Subject: [PATCH] Make Touchables strict mode compatible

Convert all Touchables to be class based and remove UNSAFE props. Also renamed
Touchable.Mixin.withoutDefaultFocusAndBlur to Touchable.MixinWithoutDefaultFocusAndBlur
to improve flow automatic typings.

Note: TouchableNativeFeedback uses ReactNative.findNodeHandle which triggers a
warning in strict mode during a tap. I could not figure out how to remove the need for
that call.

Related to https://github.com/facebook/react-native/issues/22186
---
 .../Animated/src/createAnimatedComponent.js   |  12 +-
 Libraries/Components/Touchable/Touchable.js   |  40 +-
 .../Components/Touchable/TouchableBounce.js   | 192 +++++----
 .../Touchable/TouchableHighlight.js           | 350 +++++++++--------
 .../TouchableNativeFeedback.android.js        | 370 +++++++++---------
 .../Components/Touchable/TouchableOpacity.js  | 268 +++++++------
 .../Touchable/TouchableWithoutFeedback.js     | 282 +++++++------
 RNTester/js/StrictModeExample.js              |  68 +++-
 8 files changed, 890 insertions(+), 692 deletions(-)

diff --git a/Libraries/Animated/src/createAnimatedComponent.js b/Libraries/Animated/src/createAnimatedComponent.js
index 327070de59da40..8872a594d60e25 100644
--- a/Libraries/Animated/src/createAnimatedComponent.js
+++ b/Libraries/Animated/src/createAnimatedComponent.js
@@ -35,6 +35,8 @@ function createAnimatedComponent(Component: any): any {
 
     constructor(props: Object) {
       super(props);
+
+      this._attachProps(this.props);
     }
 
     componentWillUnmount() {
@@ -46,10 +48,6 @@ function createAnimatedComponent(Component: any): any {
       this._component.setNativeProps(props);
     }
 
-    UNSAFE_componentWillMount() {
-      this._attachProps(this.props);
-    }
-
     componentDidMount() {
       if (this._invokeAnimatedPropsCallbackOnMount) {
         this._invokeAnimatedPropsCallbackOnMount = false;
@@ -131,11 +129,9 @@ function createAnimatedComponent(Component: any): any {
       oldPropsAnimated && oldPropsAnimated.__detach();
     }
 
-    UNSAFE_componentWillReceiveProps(newProps) {
-      this._attachProps(newProps);
-    }
-
     componentDidUpdate(prevProps) {
+      this._attachProps(this.props);
+
       if (this._component !== this._prevComponent) {
         this._propsAnimated.setNativeView(this._component);
       }
diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js
index 72f7e4c35033f5..e5cea837dba1e4 100644
--- a/Libraries/Components/Touchable/Touchable.js
+++ b/Libraries/Components/Touchable/Touchable.js
@@ -23,7 +23,7 @@ const View = require('View');
 const keyMirror = require('fbjs/lib/keyMirror');
 const normalizeColor = require('normalizeColor');
 
-import type {PressEvent} from 'CoreEventTypes';
+import type {SyntheticEvent, PressEvent} from 'CoreEventTypes';
 import type {EdgeInsetsProp} from 'EdgeInsetsPropType';
 
 const extractSingleTouch = nativeEvent => {
@@ -149,6 +149,22 @@ type State =
   | typeof States.RESPONDER_ACTIVE_LONG_PRESS_OUT
   | typeof States.ERROR;
 
+export type TouchableState = {|
+  touchable: {
+    touchState: ?State,
+    responderID: ?number,
+  },
+|};
+
+type TargetEvent = SyntheticEvent<
+  $ReadOnly<{|
+    target: number,
+  |}>,
+>;
+
+export type BlurEvent = TargetEvent;
+export type FocusEvent = TargetEvent;
+
 /*
  * Quick lookup map for states that are considered to be "active"
  */
@@ -576,9 +592,9 @@ const TouchableMixin = {
    * currently has the focus. Most platforms only support a single element being
    * focused at a time, in which case there may have been a previously focused
    * element that was blurred just prior to this. This can be overridden when
-   * using `Touchable.Mixin.withoutDefaultFocusAndBlur`.
+   * using `Touchable.MixinWithoutDefaultFocusAndBlur`.
    */
-  touchableHandleFocus: function(e: Event) {
+  touchableHandleFocus: function(e: FocusEvent) {
     this.props.onFocus && this.props.onFocus(e);
   },
 
@@ -588,9 +604,9 @@ const TouchableMixin = {
    * no longer has focus. Most platforms only support a single element being
    * focused at a time, in which case the focus may have moved to another.
    * This can be overridden when using
-   * `Touchable.Mixin.withoutDefaultFocusAndBlur`.
+   * `Touchable.MixinWithoutDefaultFocusAndBlur`.
    */
-  touchableHandleBlur: function(e: Event) {
+  touchableHandleBlur: function(e: BlurEvent) {
     this.props.onBlur && this.props.onBlur(e);
   },
 
@@ -900,8 +916,6 @@ const TouchableMixin = {
       }
     }
   },
-
-  withoutDefaultFocusAndBlur: {},
 };
 
 /**
@@ -910,15 +924,13 @@ const TouchableMixin = {
  * be set on TV platforms, without breaking existing implementations of
  * `Touchable`.
  */
-const {
-  touchableHandleFocus,
-  touchableHandleBlur,
-  ...TouchableMixinWithoutDefaultFocusAndBlur
-} = TouchableMixin;
-TouchableMixin.withoutDefaultFocusAndBlur = TouchableMixinWithoutDefaultFocusAndBlur;
+const TouchableMixinWithoutDefaultFocusAndBlur = {...TouchableMixin};
+delete TouchableMixinWithoutDefaultFocusAndBlur.touchableHandleFocus;
+delete TouchableMixinWithoutDefaultFocusAndBlur.touchableHandleBlur;
 
 const Touchable = {
   Mixin: TouchableMixin,
+  MixinWithoutDefaultFocusAndBlur: TouchableMixinWithoutDefaultFocusAndBlur,
   TOUCH_TARGET_DEBUG: false, // Highlights all touchable targets. Toggle with Inspector.
   /**
    * Renders a debugging overlay to visualize touch target with hitSlop (might not work on Android).
@@ -928,7 +940,7 @@ const Touchable = {
     hitSlop,
   }: {
     color: string | number,
-    hitSlop: EdgeInsetsProp,
+    hitSlop: ?EdgeInsetsProp,
   }) => {
     if (!Touchable.TOUCH_TARGET_DEBUG) {
       return null;
diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js
index 04817aef02bacf..16ef42f85c87ee 100644
--- a/Libraries/Components/Touchable/TouchableBounce.js
+++ b/Libraries/Components/Touchable/TouchableBounce.js
@@ -10,40 +10,64 @@
 'use strict';
 
 const Animated = require('Animated');
-const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes');
-const DeprecatedEdgeInsetsPropType = require('DeprecatedEdgeInsetsPropType');
-const NativeMethodsMixin = require('NativeMethodsMixin');
 const Platform = require('Platform');
-const PropTypes = require('prop-types');
 const React = require('React');
 const Touchable = require('Touchable');
-const TouchableWithoutFeedback = require('TouchableWithoutFeedback');
-
-const createReactClass = require('create-react-class');
 
+import type {PressEvent} from 'CoreEventTypes';
 import type {EdgeInsetsProp} from 'EdgeInsetsPropType';
 import type {ViewStyleProp} from 'StyleSheet';
+import type {BlurEvent, FocusEvent, TouchableState} from 'Touchable';
 import type {Props as TouchableWithoutFeedbackProps} from 'TouchableWithoutFeedback';
-import type {PressEvent} from 'CoreEventTypes';
-
-type State = {
-  animationID: ?number,
-  scale: Animated.Value,
-};
 
 const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
 
 type Props = $ReadOnly<{|
   ...TouchableWithoutFeedbackProps,
 
+  // The function passed takes a callback to start the animation which should
+  // be run after this onPress handler is done. You can use this (for example)
+  // to update UI before starting the animation.
   onPressWithCompletion?: ?(fn: () => void) => void,
+  // the function passed is called after the animation is complete
   onPressAnimationComplete?: ?() => void,
+  /**
+   * When the scroll view is disabled, this defines how far your touch may
+   * move off of the button, before deactivating the button. Once deactivated,
+   * try moving it back and you'll see that the button is once again
+   * reactivated! Move it back and forth several times while the scroll view
+   * is disabled. Ensure you pass in a constant to reduce memory allocations.
+   */
   pressRetentionOffset?: ?EdgeInsetsProp,
-  releaseVelocity?: ?number,
-  releaseBounciness?: ?number,
+  releaseVelocity: number,
+  releaseBounciness: number,
+  /**
+   * Style to apply to the container/underlay. Most commonly used to make sure
+   * rounded corners match the wrapped component.
+   */
   style?: ?ViewStyleProp,
 |}>;
 
+type State = {|
+  scale: Animated.Value,
+
+  ...TouchableState,
+|};
+
+function createTouchMixin(
+  node: React.ElementRef<typeof TouchableBounce>,
+): typeof Touchable.MixinWithoutDefaultFocusAndBlur {
+  const touchMixin = {...Touchable.MixinWithoutDefaultFocusAndBlur};
+
+  for (const key in touchMixin) {
+    if (typeof touchMixin[key] === 'function') {
+      touchMixin[key] = touchMixin[key].bind(node);
+    }
+  }
+
+  return touchMixin;
+}
+
 /**
  * Example of using the `TouchableMixin` to play well with other responder
  * locking views including `ScrollView`. `TouchableMixin` provides touchable
@@ -51,50 +75,50 @@ type Props = $ReadOnly<{|
  * `TouchableMixin` expects us to implement some abstract methods to handle
  * interesting interactions such as `handleTouchablePress`.
  */
-const TouchableBounce = ((createReactClass({
-  displayName: 'TouchableBounce',
-  mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur, NativeMethodsMixin],
-
-  propTypes: {
-    /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
-     * error found when Flow v0.89 was deployed. To see the error, delete this
-     * comment and run Flow. */
-    ...TouchableWithoutFeedback.propTypes,
-    // The function passed takes a callback to start the animation which should
-    // be run after this onPress handler is done. You can use this (for example)
-    // to update UI before starting the animation.
-    onPressWithCompletion: PropTypes.func,
-    // the function passed is called after the animation is complete
-    onPressAnimationComplete: PropTypes.func,
-    /**
-     * When the scroll view is disabled, this defines how far your touch may
-     * move off of the button, before deactivating the button. Once deactivated,
-     * try moving it back and you'll see that the button is once again
-     * reactivated! Move it back and forth several times while the scroll view
-     * is disabled. Ensure you pass in a constant to reduce memory allocations.
-     */
-    pressRetentionOffset: DeprecatedEdgeInsetsPropType,
-    releaseVelocity: PropTypes.number.isRequired,
-    releaseBounciness: PropTypes.number.isRequired,
-    /**
-     * Style to apply to the container/underlay. Most commonly used to make sure
-     * rounded corners match the wrapped component.
-     */
-    style: DeprecatedViewPropTypes.style,
-  },
-
-  getDefaultProps: function() {
-    return {releaseBounciness: 10, releaseVelocity: 10};
-  },
-
-  getInitialState: function(): State {
-    return {
-      ...this.touchableGetInitialState(),
+class TouchableBounce extends React.Component<Props, State> {
+  static defaultProps = {
+    releaseBounciness: 10,
+    releaseVelocity: 10,
+  };
+
+  _touchMixin: typeof Touchable.MixinWithoutDefaultFocusAndBlur = createTouchMixin(this);
+
+  constructor(props: Props) {
+    super(props);
+
+    const touchMixin = Touchable.MixinWithoutDefaultFocusAndBlur;
+    for (const key in touchMixin) {
+      if (
+        typeof touchMixin[key] === 'function' &&
+        (key.startsWith('_') || key.startsWith('touchable'))
+      ) {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key].bind(this);
+      }
+    }
+
+    Object.keys(touchMixin)
+      .filter(key => typeof touchMixin[key] !== 'function')
+      .forEach(key => {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key];
+      });
+
+    this.state = {
       scale: new Animated.Value(1),
+      ...this._touchMixin.touchableGetInitialState(),
     };
-  },
+  }
+
+  componentDidMount() {
+    this._touchMixin.componentDidMount();
+  }
 
-  bounceTo: function(
+  componentWillUnmount() {
+    this._touchMixin.componentWillUnmount();
+  }
+
+  bounceTo(
     value: number,
     velocity: number,
     bounciness: number,
@@ -106,37 +130,37 @@ const TouchableBounce = ((createReactClass({
       bounciness,
       useNativeDriver: true,
     }).start(callback);
-  },
+  }
 
   /**
    * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
    * defined on your component.
    */
-  touchableHandleActivePressIn: function(e: PressEvent) {
+  touchableHandleActivePressIn(e: PressEvent) {
     this.bounceTo(0.93, 0.1, 0);
     this.props.onPressIn && this.props.onPressIn(e);
-  },
+  }
 
-  touchableHandleActivePressOut: function(e: PressEvent) {
+  touchableHandleActivePressOut(e: PressEvent) {
     this.bounceTo(1, 0.4, 0);
     this.props.onPressOut && this.props.onPressOut(e);
-  },
+  }
 
-  touchableHandleFocus: function(e: Event) {
+  touchableHandleFocus(e: FocusEvent) {
     if (Platform.isTV) {
       this.bounceTo(0.93, 0.1, 0);
     }
     this.props.onFocus && this.props.onFocus(e);
-  },
+  }
 
-  touchableHandleBlur: function(e: Event) {
+  touchableHandleBlur(e: BlurEvent) {
     if (Platform.isTV) {
       this.bounceTo(1, 0.4, 0);
     }
     this.props.onBlur && this.props.onBlur(e);
-  },
+  }
 
-  touchableHandlePress: function(e: PressEvent) {
+  touchableHandlePress(e: PressEvent) {
     const onPressWithCompletion = this.props.onPressWithCompletion;
     if (onPressWithCompletion) {
       onPressWithCompletion(() => {
@@ -158,21 +182,21 @@ const TouchableBounce = ((createReactClass({
       this.props.onPressAnimationComplete,
     );
     this.props.onPress && this.props.onPress(e);
-  },
+  }
 
-  touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
+  touchableGetPressRectOffset(): EdgeInsetsProp {
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
-  },
+  }
 
-  touchableGetHitSlop: function(): ?EdgeInsetsProp {
+  touchableGetHitSlop(): ?EdgeInsetsProp {
     return this.props.hitSlop;
-  },
+  }
 
-  touchableGetHighlightDelayMS: function(): number {
+  touchableGetHighlightDelayMS(): number {
     return 0;
-  },
+  }
 
-  render: function(): React.Element<any> {
+  render(): React.Element<any> {
     return (
       <Animated.View
         style={[{transform: [{scale: this.state.scale}]}, this.props.style]}
@@ -184,14 +208,18 @@ const TouchableBounce = ((createReactClass({
         nativeID={this.props.nativeID}
         testID={this.props.testID}
         hitSlop={this.props.hitSlop}
-        onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
+        onStartShouldSetResponder={
+          this._touchMixin.touchableHandleStartShouldSetResponder
+        }
         onResponderTerminationRequest={
-          this.touchableHandleResponderTerminationRequest
+          this._touchMixin.touchableHandleResponderTerminationRequest
         }
-        onResponderGrant={this.touchableHandleResponderGrant}
-        onResponderMove={this.touchableHandleResponderMove}
-        onResponderRelease={this.touchableHandleResponderRelease}
-        onResponderTerminate={this.touchableHandleResponderTerminate}>
+        onResponderGrant={this._touchMixin.touchableHandleResponderGrant}
+        onResponderMove={this._touchMixin.touchableHandleResponderMove}
+        onResponderRelease={this._touchMixin.touchableHandleResponderRelease}
+        onResponderTerminate={
+          this._touchMixin.touchableHandleResponderTerminate
+        }>
         {this.props.children}
         {Touchable.renderDebugView({
           color: 'orange',
@@ -199,7 +227,7 @@ const TouchableBounce = ((createReactClass({
         })}
       </Animated.View>
     );
-  },
-}): any): React.ComponentType<Props>);
+  }
+}
 
 module.exports = TouchableBounce;
diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js
index fa0e083fa8a985..48228c5138b6db 100644
--- a/Libraries/Components/Touchable/TouchableHighlight.js
+++ b/Libraries/Components/Touchable/TouchableHighlight.js
@@ -9,45 +9,78 @@
  */
 'use strict';
 
-const DeprecatedColorPropType = require('DeprecatedColorPropType');
-const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes');
-const NativeMethodsMixin = require('NativeMethodsMixin');
 const Platform = require('Platform');
-const PropTypes = require('prop-types');
 const React = require('React');
 const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
 const StyleSheet = require('StyleSheet');
 const Touchable = require('Touchable');
-const TouchableWithoutFeedback = require('TouchableWithoutFeedback');
 const View = require('View');
 
-const createReactClass = require('create-react-class');
 const ensurePositiveDelayProps = require('ensurePositiveDelayProps');
 
 import type {PressEvent} from 'CoreEventTypes';
 import type {ViewStyleProp} from 'StyleSheet';
 import type {ColorValue} from 'StyleSheetTypes';
+import type {BlurEvent, FocusEvent, TouchableState} from 'Touchable';
 import type {Props as TouchableWithoutFeedbackProps} from 'TouchableWithoutFeedback';
 import type {TVParallaxPropertiesType} from 'TVViewPropTypes';
 
-const DEFAULT_PROPS = {
-  activeOpacity: 0.85,
-  delayPressOut: 100,
-  underlayColor: 'black',
-};
-
 const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
 
 type IOSProps = $ReadOnly<{|
-  hasTVPreferredFocus?: ?boolean,
-  tvParallaxProperties?: ?TVParallaxPropertiesType,
+  /**
+   * *(Apple TV only)* TV preferred focus (see documentation for the View component).
+   *
+   * @platform ios
+   */
+  hasTVPreferredFocus?: boolean,
+  /**
+   * *(Apple TV only)* Object with properties to control Apple TV parallax effects.
+   *
+   * enabled: If true, parallax effects are enabled.  Defaults to true.
+   * shiftDistanceX: Defaults to 2.0.
+   * shiftDistanceY: Defaults to 2.0.
+   * tiltAngle: Defaults to 0.05.
+   * magnification: Defaults to 1.0.
+   * pressMagnification: Defaults to 1.0.
+   * pressDuration: Defaults to 0.3.
+   * pressDelay: Defaults to 0.0.
+   *
+   * @platform ios
+   */
+  tvParallaxProperties?: TVParallaxPropertiesType,
 |}>;
 
 type AndroidProps = $ReadOnly<{|
+  /**
+   * TV next focus down (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusDown?: ?number,
+  /**
+   * TV next focus forward (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusForward?: ?number,
+  /**
+   * TV next focus left (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusLeft?: ?number,
+  /**
+   * TV next focus right (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusRight?: ?number,
+  /**
+   * TV next focus up (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusUp?: ?number,
 |}>;
 
@@ -56,14 +89,64 @@ type Props = $ReadOnly<{|
   ...IOSProps,
   ...AndroidProps,
 
-  activeOpacity?: ?number,
-  underlayColor?: ?ColorValue,
+  /**
+   * Determines what the opacity of the wrapped view should be when touch is
+   * active.
+   */
+  activeOpacity: number,
+  /**
+   * The color of the underlay that will show through when the touch is
+   * active.
+   */
+  underlayColor: ColorValue,
+  /**
+   * Delay in ms, from the release of the touch, before onPressOut is called.
+   */
+  delayPressOut: number,
+  /**
+   * Style to apply to the container/underlay. Most commonly used to make sure
+   * rounded corners match the wrapped component.
+   */
   style?: ?ViewStyleProp,
+  /**
+   * Called immediately after the underlay is shown
+   */
   onShowUnderlay?: ?() => void,
+  /**
+   * Called immediately after the underlay is hidden
+   */
   onHideUnderlay?: ?() => void,
+  /**
+   * Handy for snapshot tests.
+   */
   testOnly_pressed?: ?boolean,
 |}>;
 
+type State = {|
+  ...TouchableState,
+
+  extraChildStyle: ?{
+    opacity: number,
+  },
+  extraUnderlayStyle: ?{|
+    backgroundColor: ColorValue,
+  |},
+|};
+
+function createTouchMixin(
+  node: React.ElementRef<typeof TouchableHighlight>,
+): typeof Touchable.MixinWithoutDefaultFocusAndBlur {
+  const touchMixin = {...Touchable.MixinWithoutDefaultFocusAndBlur};
+
+  for (const key in touchMixin) {
+    if (typeof touchMixin[key] === 'function') {
+      touchMixin[key] = touchMixin[key].bind(node);
+    }
+  }
+
+  return touchMixin;
+}
+
 /**
  * A wrapper for making views respond properly to touches.
  * On press down, the opacity of the wrapped view is decreased, which allows
@@ -160,103 +243,46 @@ type Props = $ReadOnly<{|
  * ```
  *
  */
+class TouchableHighlight extends React.Component<Props, State> {
+  static defaultProps = {
+    activeOpacity: 0.85,
+    delayPressOut: 100,
+    underlayColor: 'black',
+  };
+
+  _touchMixin: typeof Touchable.MixinWithoutDefaultFocusAndBlur = createTouchMixin(
+    this,
+  );
+
+  _isMounted: boolean;
+
+  _hideTimeout: ?TimeoutID;
+
+  constructor(props: Props) {
+    super(props);
+
+    const touchMixin = Touchable.MixinWithoutDefaultFocusAndBlur;
+    for (const key in touchMixin) {
+      if (
+        typeof touchMixin[key] === 'function' &&
+        (key.startsWith('_') || key.startsWith('touchable'))
+      ) {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key].bind(this);
+      }
+    }
 
-const TouchableHighlight = ((createReactClass({
-  displayName: 'TouchableHighlight',
-  propTypes: {
-    /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
-     * error found when Flow v0.89 was deployed. To see the error, delete this
-     * comment and run Flow. */
-    ...TouchableWithoutFeedback.propTypes,
-    /**
-     * Determines what the opacity of the wrapped view should be when touch is
-     * active.
-     */
-    activeOpacity: PropTypes.number,
-    /**
-     * The color of the underlay that will show through when the touch is
-     * active.
-     */
-    underlayColor: DeprecatedColorPropType,
-    /**
-     * Style to apply to the container/underlay. Most commonly used to make sure
-     * rounded corners match the wrapped component.
-     */
-    style: DeprecatedViewPropTypes.style,
-    /**
-     * Called immediately after the underlay is shown
-     */
-    onShowUnderlay: PropTypes.func,
-    /**
-     * Called immediately after the underlay is hidden
-     */
-    onHideUnderlay: PropTypes.func,
-    /**
-     * *(Apple TV only)* TV preferred focus (see documentation for the View component).
-     *
-     * @platform ios
-     */
-    hasTVPreferredFocus: PropTypes.bool,
-    /**
-     * TV next focus down (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusDown: PropTypes.number,
-    /**
-     * TV next focus forward (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusForward: PropTypes.number,
-    /**
-     * TV next focus left (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusLeft: PropTypes.number,
-    /**
-     * TV next focus right (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusRight: PropTypes.number,
-    /**
-     * TV next focus up (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusUp: PropTypes.number,
-    /**
-     * *(Apple TV only)* Object with properties to control Apple TV parallax effects.
-     *
-     * enabled: If true, parallax effects are enabled.  Defaults to true.
-     * shiftDistanceX: Defaults to 2.0.
-     * shiftDistanceY: Defaults to 2.0.
-     * tiltAngle: Defaults to 0.05.
-     * magnification: Defaults to 1.0.
-     * pressMagnification: Defaults to 1.0.
-     * pressDuration: Defaults to 0.3.
-     * pressDelay: Defaults to 0.0.
-     *
-     * @platform ios
-     */
-    tvParallaxProperties: PropTypes.object,
-    /**
-     * Handy for snapshot tests.
-     */
-    testOnly_pressed: PropTypes.bool,
-  },
-
-  mixins: [NativeMethodsMixin, Touchable.Mixin.withoutDefaultFocusAndBlur],
-
-  getDefaultProps: () => DEFAULT_PROPS,
+    Object.keys(touchMixin)
+      .filter(key => typeof touchMixin[key] !== 'function')
+      .forEach(key => {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key];
+      });
 
-  getInitialState: function() {
     this._isMounted = false;
     if (this.props.testOnly_pressed) {
-      return {
-        ...this.touchableGetInitialState(),
+      this.state = {
+        ...this._touchMixin.touchableGetInitialState(),
         extraChildStyle: {
           opacity: this.props.activeOpacity,
         },
@@ -265,66 +291,70 @@ const TouchableHighlight = ((createReactClass({
         },
       };
     } else {
-      return {
-        ...this.touchableGetInitialState(),
+      this.state = {
+        ...this._touchMixin.touchableGetInitialState(),
         extraChildStyle: null,
         extraUnderlayStyle: null,
       };
     }
-  },
+  }
 
-  componentDidMount: function() {
+  componentDidMount() {
     this._isMounted = true;
     ensurePositiveDelayProps(this.props);
-  },
 
-  componentWillUnmount: function() {
+    this._touchMixin.componentDidMount();
+  }
+
+  componentDidUpdate(prevProps: Props, prevState: State) {
+    ensurePositiveDelayProps(this.props);
+  }
+
+  componentWillUnmount() {
     this._isMounted = false;
     clearTimeout(this._hideTimeout);
-  },
 
-  UNSAFE_componentWillReceiveProps: function(nextProps) {
-    ensurePositiveDelayProps(nextProps);
-  },
+    this._touchMixin.componentWillUnmount();
+  }
 
-  viewConfig: {
+  static viewConfig = {
     uiViewClassName: 'RCTView',
     validAttributes: ReactNativeViewAttributes.RCTView,
-  },
+  };
 
   /**
    * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
    * defined on your component.
    */
-  touchableHandleActivePressIn: function(e: PressEvent) {
+  touchableHandleActivePressIn(e: PressEvent) {
     clearTimeout(this._hideTimeout);
     this._hideTimeout = null;
     this._showUnderlay();
     this.props.onPressIn && this.props.onPressIn(e);
-  },
+  }
 
-  touchableHandleActivePressOut: function(e: PressEvent) {
+  touchableHandleActivePressOut(e: PressEvent) {
     if (!this._hideTimeout) {
       this._hideUnderlay();
     }
     this.props.onPressOut && this.props.onPressOut(e);
-  },
+  }
 
-  touchableHandleFocus: function(e: Event) {
+  touchableHandleFocus(e: FocusEvent) {
     if (Platform.isTV) {
       this._showUnderlay();
     }
     this.props.onFocus && this.props.onFocus(e);
-  },
+  }
 
-  touchableHandleBlur: function(e: Event) {
+  touchableHandleBlur(e: BlurEvent) {
     if (Platform.isTV) {
       this._hideUnderlay();
     }
     this.props.onBlur && this.props.onBlur(e);
-  },
+  }
 
-  touchableHandlePress: function(e: PressEvent) {
+  touchableHandlePress(e: PressEvent) {
     clearTimeout(this._hideTimeout);
     if (!Platform.isTV) {
       this._showUnderlay();
@@ -334,33 +364,33 @@ const TouchableHighlight = ((createReactClass({
       );
     }
     this.props.onPress && this.props.onPress(e);
-  },
+  }
 
-  touchableHandleLongPress: function(e: PressEvent) {
+  touchableHandleLongPress(e: PressEvent) {
     this.props.onLongPress && this.props.onLongPress(e);
-  },
+  }
 
-  touchableGetPressRectOffset: function() {
+  touchableGetPressRectOffset() {
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
-  },
+  }
 
-  touchableGetHitSlop: function() {
+  touchableGetHitSlop() {
     return this.props.hitSlop;
-  },
+  }
 
-  touchableGetHighlightDelayMS: function() {
+  touchableGetHighlightDelayMS() {
     return this.props.delayPressIn;
-  },
+  }
 
-  touchableGetLongPressDelayMS: function() {
+  touchableGetLongPressDelayMS() {
     return this.props.delayLongPress;
-  },
+  }
 
-  touchableGetPressOutDelayMS: function() {
+  touchableGetPressOutDelayMS() {
     return this.props.delayPressOut;
-  },
+  }
 
-  _showUnderlay: function() {
+  _showUnderlay() {
     if (!this._isMounted || !this._hasPressHandler()) {
       return;
     }
@@ -373,9 +403,9 @@ const TouchableHighlight = ((createReactClass({
       },
     });
     this.props.onShowUnderlay && this.props.onShowUnderlay();
-  },
+  }
 
-  _hideUnderlay: function() {
+  _hideUnderlay = () => {
     clearTimeout(this._hideTimeout);
     this._hideTimeout = null;
     if (this.props.testOnly_pressed) {
@@ -388,18 +418,18 @@ const TouchableHighlight = ((createReactClass({
       });
       this.props.onHideUnderlay && this.props.onHideUnderlay();
     }
-  },
+  };
 
-  _hasPressHandler: function() {
+  _hasPressHandler() {
     return !!(
       this.props.onPress ||
       this.props.onPressIn ||
       this.props.onPressOut ||
       this.props.onLongPress
     );
-  },
+  }
 
-  render: function() {
+  render() {
     const child = React.Children.only(this.props.children);
     return (
       <View
@@ -422,14 +452,18 @@ const TouchableHighlight = ((createReactClass({
         nextFocusLeft={this.props.nextFocusLeft}
         nextFocusRight={this.props.nextFocusRight}
         nextFocusUp={this.props.nextFocusUp}
-        onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
+        onStartShouldSetResponder={
+          this._touchMixin.touchableHandleStartShouldSetResponder
+        }
         onResponderTerminationRequest={
-          this.touchableHandleResponderTerminationRequest
+          this._touchMixin.touchableHandleResponderTerminationRequest
+        }
+        onResponderGrant={this._touchMixin.touchableHandleResponderGrant}
+        onResponderMove={this._touchMixin.touchableHandleResponderMove}
+        onResponderRelease={this._touchMixin.touchableHandleResponderRelease}
+        onResponderTerminate={
+          this._touchMixin.touchableHandleResponderTerminate
         }
-        onResponderGrant={this.touchableHandleResponderGrant}
-        onResponderMove={this.touchableHandleResponderMove}
-        onResponderRelease={this.touchableHandleResponderRelease}
-        onResponderTerminate={this.touchableHandleResponderTerminate}
         nativeID={this.props.nativeID}
         testID={this.props.testID}>
         {React.cloneElement(child, {
@@ -444,7 +478,7 @@ const TouchableHighlight = ((createReactClass({
         })}
       </View>
     );
-  },
-}): any): React.ComponentType<Props>);
+  }
+}
 
 module.exports = TouchableHighlight;
diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js
index 77b6e84ea7c60f..15dfefce3a4345 100644
--- a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js
+++ b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js
@@ -12,36 +12,85 @@
 
 const Platform = require('Platform');
 const React = require('React');
-const PropTypes = require('prop-types');
 const ReactNative = require('ReactNative');
 const Touchable = require('Touchable');
-const TouchableWithoutFeedback = require('TouchableWithoutFeedback');
 const UIManager = require('UIManager');
 const View = require('View');
 
-const createReactClass = require('create-react-class');
 const ensurePositiveDelayProps = require('ensurePositiveDelayProps');
 const processColor = require('processColor');
 
 import type {PressEvent} from 'CoreEventTypes';
+import type {TouchableState} from 'Touchable';
 
-const rippleBackgroundPropType = PropTypes.shape({
-  type: PropTypes.oneOf(['RippleAndroid']),
-  color: PropTypes.number,
-  borderless: PropTypes.bool,
-});
+const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
 
-const themeAttributeBackgroundPropType = PropTypes.shape({
-  type: PropTypes.oneOf(['ThemeAttrAndroid']),
-  attribute: PropTypes.string.isRequired,
-});
+type Props = $ReadOnly<{|
+  ...TouchableWithoutFeedbackProps,
 
-const backgroundPropType = PropTypes.oneOfType([
-  rippleBackgroundPropType,
-  themeAttributeBackgroundPropType,
-]);
+  /**
+   * Determines the type of background drawable that's going to be used to
+   * display feedback. It takes an object with `type` property and extra data
+   * depending on the `type`. It's recommended to use one of the static
+   * methods to generate that dictionary.
+   */
+  background?: ?AndroidDrawableRipple,
 
-const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
+  /**
+   * TV preferred focus (see documentation for the View component).
+   */
+  hasTVPreferredFocus?: ?boolean,
+
+  /**
+   * TV next focus down (see documentation for the View component).
+   */
+  nextFocusDown?: ?number,
+
+  /**
+   * TV next focus forward (see documentation for the View component).
+   */
+  nextFocusForward?: ?number,
+
+  /**
+   * TV next focus left (see documentation for the View component).
+   */
+  nextFocusLeft?: ?number,
+
+  /**
+   * TV next focus right (see documentation for the View component).
+   */
+  nextFocusRight?: ?number,
+
+  /**
+   * TV next focus up (see documentation for the View component).
+   */
+  nextFocusUp?: ?number,
+
+  /**
+   * Set to true to add the ripple effect to the foreground of the view, instead of the
+   * background. This is useful if one of your child views has a background of its own, or you're
+   * e.g. displaying images, and you don't want the ripple to be covered by them.
+   *
+   * Check TouchableNativeFeedback.canUseNativeForeground() first, as this is only available on
+   * Android 6.0 and above. If you try to use this on older versions you will get a warning and
+   * fallback to background.
+   */
+  useForeground?: ?boolean,
+|}>;
+
+function createTouchMixin(
+  node: React.ElementRef<typeof TouchableNativeFeedback>,
+): typeof Touchable.Mixin {
+  const touchMixin = {...Touchable.Mixin};
+
+  for (const key in touchMixin) {
+    if (typeof touchMixin[key] === 'function') {
+      touchMixin[key] = touchMixin[key].bind(node);
+    }
+  }
+
+  return touchMixin;
+}
 
 /**
  * A wrapper for making views respond properly to touches (Android only).
@@ -71,145 +120,56 @@ const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
  * },
  * ```
  */
+class TouchableNativeFeedback extends React.Component<Props, TouchableState> {
+  static defaultProps = {
+    background: TouchableNativeFeedback.SelectableBackground(),
+  };
+
+  _touchMixin: typeof Touchable.Mixin = createTouchMixin(this);
+
+  constructor(props: Props) {
+    super(props);
+
+    const touchMixin = Touchable.Mixin;
+    for (const key in touchMixin) {
+      if (
+        typeof touchMixin[key] === 'function' &&
+        (key.startsWith('_') || key.startsWith('touchable'))
+      ) {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key].bind(this);
+      }
+    }
 
-const TouchableNativeFeedback = createReactClass({
-  displayName: 'TouchableNativeFeedback',
-  propTypes: {
-    /* $FlowFixMe(>=0.89.0 site=react_native_android_fb) This comment
-     * suppresses an error found when Flow v0.89 was deployed. To see the
-     * error, delete this comment and run Flow. */
-    ...TouchableWithoutFeedback.propTypes,
-
-    /**
-     * Determines the type of background drawable that's going to be used to
-     * display feedback. It takes an object with `type` property and extra data
-     * depending on the `type`. It's recommended to use one of the static
-     * methods to generate that dictionary.
-     */
-    background: backgroundPropType,
-
-    /**
-     * TV preferred focus (see documentation for the View component).
-     */
-    hasTVPreferredFocus: PropTypes.bool,
-
-    /**
-     * TV next focus down (see documentation for the View component).
-     */
-    nextFocusDown: PropTypes.number,
-
-    /**
-     * TV next focus forward (see documentation for the View component).
-     */
-    nextFocusForward: PropTypes.number,
-
-    /**
-     * TV next focus left (see documentation for the View component).
-     */
-    nextFocusLeft: PropTypes.number,
-
-    /**
-     * TV next focus right (see documentation for the View component).
-     */
-    nextFocusRight: PropTypes.number,
-
-    /**
-     * TV next focus up (see documentation for the View component).
-     */
-    nextFocusUp: PropTypes.number,
-
-    /**
-     * Set to true to add the ripple effect to the foreground of the view, instead of the
-     * background. This is useful if one of your child views has a background of its own, or you're
-     * e.g. displaying images, and you don't want the ripple to be covered by them.
-     *
-     * Check TouchableNativeFeedback.canUseNativeForeground() first, as this is only available on
-     * Android 6.0 and above. If you try to use this on older versions you will get a warning and
-     * fallback to background.
-     */
-    useForeground: PropTypes.bool,
-  },
-
-  statics: {
-    /**
-     * Creates an object that represents android theme's default background for
-     * selectable elements (?android:attr/selectableItemBackground).
-     */
-    SelectableBackground: function(): {
-      type: 'ThemeAttrAndroid',
-      attribute: 'selectableItemBackground',
-    } {
-      return {type: 'ThemeAttrAndroid', attribute: 'selectableItemBackground'};
-    },
-    /**
-     * Creates an object that represent android theme's default background for borderless
-     * selectable elements (?android:attr/selectableItemBackgroundBorderless).
-     * Available on android API level 21+.
-     */
-    SelectableBackgroundBorderless: function(): {
-      type: 'ThemeAttrAndroid',
-      attribute: 'selectableItemBackgroundBorderless',
-    } {
-      return {
-        type: 'ThemeAttrAndroid',
-        attribute: 'selectableItemBackgroundBorderless',
-      };
-    },
-    /**
-     * Creates an object that represents ripple drawable with specified color (as a
-     * string). If property `borderless` evaluates to true the ripple will
-     * render outside of the view bounds (see native actionbar buttons as an
-     * example of that behavior). This background type is available on Android
-     * API level 21+.
-     *
-     * @param color The ripple color
-     * @param borderless If the ripple can render outside it's bounds
-     */
-    Ripple: function(
-      color: string,
-      borderless: boolean,
-    ): {
-      type: 'RippleAndroid',
-      color: ?number,
-      borderless: boolean,
-    } {
-      return {
-        type: 'RippleAndroid',
-        color: processColor(color),
-        borderless: borderless,
-      };
-    },
-
-    canUseNativeForeground: function(): boolean {
-      return Platform.OS === 'android' && Platform.Version >= 23;
-    },
-  },
-
-  mixins: [Touchable.Mixin],
-
-  getDefaultProps: function() {
-    return {
-      background: this.SelectableBackground(),
-    };
-  },
+    Object.keys(touchMixin)
+      .filter(key => typeof touchMixin[key] !== 'function')
+      .forEach(key => {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key];
+      });
+
+    this.state = this._touchMixin.touchableGetInitialState();
+  }
+
+  componentDidMount() {
+    ensurePositiveDelayProps(this.props);
 
-  getInitialState: function() {
-    return this.touchableGetInitialState();
-  },
+    this._touchMixin.componentDidMount();
+  }
 
-  componentDidMount: function() {
+  componentDidUpdate(prevProps, prevState) {
     ensurePositiveDelayProps(this.props);
-  },
+  }
 
-  UNSAFE_componentWillReceiveProps: function(nextProps) {
-    ensurePositiveDelayProps(nextProps);
-  },
+  componentWillUnmount() {
+    this._touchMixin.componentWillUnmount();
+  }
 
   /**
    * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
    * defined on your component.
    */
-  touchableHandleActivePressIn: function(e: PressEvent) {
+  touchableHandleActivePressIn(e: PressEvent) {
     this.props.onPressIn && this.props.onPressIn(e);
     this._dispatchPressedStateChange(true);
     if (this.pressInLocation) {
@@ -218,67 +178,67 @@ const TouchableNativeFeedback = createReactClass({
         this.pressInLocation.locationY,
       );
     }
-  },
+  }
 
-  touchableHandleActivePressOut: function(e: PressEvent) {
+  touchableHandleActivePressOut(e: PressEvent) {
     this.props.onPressOut && this.props.onPressOut(e);
     this._dispatchPressedStateChange(false);
-  },
+  }
 
-  touchableHandlePress: function(e: PressEvent) {
+  touchableHandlePress(e: PressEvent) {
     this.props.onPress && this.props.onPress(e);
-  },
+  }
 
-  touchableHandleLongPress: function(e: PressEvent) {
+  touchableHandleLongPress(e: PressEvent) {
     this.props.onLongPress && this.props.onLongPress(e);
-  },
+  }
 
-  touchableGetPressRectOffset: function() {
+  touchableGetPressRectOffset() {
     // Always make sure to predeclare a constant!
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
-  },
+  }
 
-  touchableGetHitSlop: function() {
+  touchableGetHitSlop() {
     return this.props.hitSlop;
-  },
+  }
 
-  touchableGetHighlightDelayMS: function() {
+  touchableGetHighlightDelayMS() {
     return this.props.delayPressIn;
-  },
+  }
 
-  touchableGetLongPressDelayMS: function() {
+  touchableGetLongPressDelayMS() {
     return this.props.delayLongPress;
-  },
+  }
 
-  touchableGetPressOutDelayMS: function() {
+  touchableGetPressOutDelayMS() {
     return this.props.delayPressOut;
-  },
+  }
 
-  _handleResponderMove: function(e) {
+  _handleResponderMove = e => {
     this.touchableHandleResponderMove(e);
     this._dispatchHotspotUpdate(
       e.nativeEvent.locationX,
       e.nativeEvent.locationY,
     );
-  },
+  };
 
-  _dispatchHotspotUpdate: function(destX, destY) {
+  _dispatchHotspotUpdate(destX, destY) {
     UIManager.dispatchViewManagerCommand(
       ReactNative.findNodeHandle(this),
       UIManager.getViewManagerConfig('RCTView').Commands.hotspotUpdate,
       [destX || 0, destY || 0],
     );
-  },
+  }
 
-  _dispatchPressedStateChange: function(pressed) {
+  _dispatchPressedStateChange(pressed) {
     UIManager.dispatchViewManagerCommand(
       ReactNative.findNodeHandle(this),
       UIManager.getViewManagerConfig('RCTView').Commands.setPressed,
       [pressed],
     );
-  },
+  }
 
-  render: function() {
+  render() {
     const child = React.Children.only(this.props.children);
     let children = child.props.children;
     if (Touchable.TOUCH_TARGET_DEBUG && child.type === View) {
@@ -325,20 +285,76 @@ const TouchableNativeFeedback = createReactClass({
       nextFocusRight: this.props.nextFocusRight,
       nextFocusUp: this.props.nextFocusUp,
       hasTVPreferredFocus: this.props.hasTVPreferredFocus,
-      onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
-      onResponderTerminationRequest: this
+      onStartShouldSetResponder: this._touchMixin
+        .touchableHandleStartShouldSetResponder,
+      onResponderTerminationRequest: this._touchMixin
         .touchableHandleResponderTerminationRequest,
-      onResponderGrant: this.touchableHandleResponderGrant,
+      onResponderGrant: this._touchMixin.touchableHandleResponderGrant,
       onResponderMove: this._handleResponderMove,
-      onResponderRelease: this.touchableHandleResponderRelease,
-      onResponderTerminate: this.touchableHandleResponderTerminate,
+      onResponderRelease: this._touchMixin.touchableHandleResponderRelease,
+      onResponderTerminate: this._touchMixin.touchableHandleResponderTerminate,
     };
 
     // We need to clone the actual element so that the ripple background drawable
     // can be applied directly to the background of this element rather than to
     // a wrapper view as done in other Touchable*
     return React.cloneElement(child, childProps);
-  },
-});
+  }
+
+  /**
+   * Creates an object that represents android theme's default background for
+   * selectable elements (?android:attr/selectableItemBackground).
+   */
+  static SelectableBackground(): {
+    type: 'ThemeAttrAndroid',
+    attribute: 'selectableItemBackground',
+  } {
+    return {type: 'ThemeAttrAndroid', attribute: 'selectableItemBackground'};
+  }
+
+  /**
+   * Creates an object that represent android theme's default background for borderless
+   * selectable elements (?android:attr/selectableItemBackgroundBorderless).
+   * Available on android API level 21+.
+   */
+  static SelectableBackgroundBorderless(): {
+    type: 'ThemeAttrAndroid',
+    attribute: 'selectableItemBackgroundBorderless',
+  } {
+    return {
+      type: 'ThemeAttrAndroid',
+      attribute: 'selectableItemBackgroundBorderless',
+    };
+  }
+
+  /**
+   * Creates an object that represents ripple drawable with specified color (as a
+   * string). If property `borderless` evaluates to true the ripple will
+   * render outside of the view bounds (see native actionbar buttons as an
+   * example of that behavior). This background type is available on Android
+   * API level 21+.
+   *
+   * @param color The ripple color
+   * @param borderless If the ripple can render outside it's bounds
+   */
+  static Ripple(
+    color: string,
+    borderless: boolean,
+  ): {
+    type: 'RippleAndroid',
+    color: ?number,
+    borderless: boolean,
+  } {
+    return {
+      type: 'RippleAndroid',
+      color: processColor(color),
+      borderless: borderless,
+    };
+  }
+
+  static canUseNativeForeground(): boolean {
+    return Platform.OS === 'android' && Platform.Version >= 23;
+  }
+}
 
 module.exports = TouchableNativeFeedback;
diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js
index 98a2c5d0afd099..4228e47d95c07a 100644
--- a/Libraries/Components/Touchable/TouchableOpacity.js
+++ b/Libraries/Components/Touchable/TouchableOpacity.js
@@ -11,18 +11,16 @@
 'use strict';
 
 const Animated = require('Animated');
+const AnimatedNode = require('AnimatedNode');
 const Easing = require('Easing');
-const NativeMethodsMixin = require('NativeMethodsMixin');
 const Platform = require('Platform');
 const React = require('React');
-const PropTypes = require('prop-types');
 const Touchable = require('Touchable');
-const TouchableWithoutFeedback = require('TouchableWithoutFeedback');
 
-const createReactClass = require('create-react-class');
 const ensurePositiveDelayProps = require('ensurePositiveDelayProps');
 const flattenStyle = require('flattenStyle');
 
+import type {BlurEvent, FocusEvent, TouchableState} from 'Touchable';
 import type {Props as TouchableWithoutFeedbackProps} from 'TouchableWithoutFeedback';
 import type {ViewStyleProp} from 'StyleSheet';
 import type {TVParallaxPropertiesType} from 'TVViewPropTypes';
@@ -31,22 +29,78 @@ import type {PressEvent} from 'CoreEventTypes';
 const PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
 
 type TVProps = $ReadOnly<{|
+  /**
+   * TV preferred focus (see documentation for the View component).
+   */
   hasTVPreferredFocus?: ?boolean,
+  /**
+   * TV next focus down (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusDown?: ?number,
+  /**
+   * TV next focus forward (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusForward?: ?number,
+  /**
+   * TV next focus left (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusLeft?: ?number,
+  /**
+   * TV next focus right (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusRight?: ?number,
+  /**
+   * TV next focus up (see documentation for the View component).
+   *
+   * @platform android
+   */
   nextFocusUp?: ?number,
+  /**
+   * Apple TV parallax effects
+   */
   tvParallaxProperties?: ?TVParallaxPropertiesType,
 |}>;
 
 type Props = $ReadOnly<{|
   ...TouchableWithoutFeedbackProps,
   ...TVProps,
-  activeOpacity?: ?number,
+
+  /**
+   * Determines what the opacity of the wrapped view should be when touch is
+   * active. Defaults to 0.2.
+   */
+  activeOpacity: number,
   style?: ?ViewStyleProp,
 |}>;
 
+type State = {|
+  ...TouchableState,
+
+  anim: Animated.Value,
+|};
+
+function createTouchMixin(
+  node: React.ElementRef<typeof TouchableOpacity>,
+): typeof Touchable.MixinWithoutDefaultFocusAndBlur {
+  const touchMixin = {...Touchable.MixinWithoutDefaultFocusAndBlur};
+
+  for (const key in touchMixin) {
+    if (typeof touchMixin[key] === 'function') {
+      touchMixin[key] = touchMixin[key].bind(node);
+    }
+  }
+
+  return touchMixin;
+}
+
 /**
  * A wrapper for making views respond properly to touches.
  * On press down, the opacity of the wrapped view is decreased, dimming it.
@@ -135,175 +189,157 @@ type Props = $ReadOnly<{|
  * ```
  *
  */
-const TouchableOpacity = ((createReactClass({
-  displayName: 'TouchableOpacity',
-  mixins: [Touchable.Mixin.withoutDefaultFocusAndBlur, NativeMethodsMixin],
-
-  propTypes: {
-    /* $FlowFixMe(>=0.89.0 site=react_native_fb) This comment suppresses an
-     * error found when Flow v0.89 was deployed. To see the error, delete this
-     * comment and run Flow. */
-    ...TouchableWithoutFeedback.propTypes,
-    /**
-     * Determines what the opacity of the wrapped view should be when touch is
-     * active. Defaults to 0.2.
-     */
-    activeOpacity: PropTypes.number,
-    /**
-     * TV preferred focus (see documentation for the View component).
-     */
-    hasTVPreferredFocus: PropTypes.bool,
-    /**
-     * TV next focus down (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusDown: PropTypes.number,
-    /**
-     * TV next focus forward (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusForward: PropTypes.number,
-    /**
-     * TV next focus left (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusLeft: PropTypes.number,
-    /**
-     * TV next focus right (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusRight: PropTypes.number,
-    /**
-     * TV next focus up (see documentation for the View component).
-     *
-     * @platform android
-     */
-    nextFocusUp: PropTypes.number,
-    /**
-     * Apple TV parallax effects
-     */
-    tvParallaxProperties: PropTypes.object,
-  },
-
-  getDefaultProps: function() {
-    return {
-      activeOpacity: 0.2,
-    };
-  },
+class TouchableOpacity extends React.Component<Props, State> {
+  static defaultProps = {
+    activeOpacity: 0.2,
+  };
+
+  _touchMixin: typeof Touchable.MixinWithoutDefaultFocusAndBlur = createTouchMixin(
+    this,
+  );
+
+  _isMounted: boolean;
+
+  constructor(props: Props) {
+    super(props);
+
+    const touchMixin = Touchable.MixinWithoutDefaultFocusAndBlur;
+    for (const key in touchMixin) {
+      if (
+        typeof touchMixin[key] === 'function' &&
+        (key.startsWith('_') || key.startsWith('touchable'))
+      ) {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key].bind(this);
+      }
+    }
+
+    Object.keys(touchMixin)
+      .filter(key => typeof touchMixin[key] !== 'function')
+      .forEach(key => {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key];
+      });
+
+    this.state = {
+      ...this._touchMixin.touchableGetInitialState(),
 
-  getInitialState: function() {
-    return {
-      ...this.touchableGetInitialState(),
       anim: new Animated.Value(this._getChildStyleOpacityWithDefault()),
     };
-  },
+  }
 
-  componentDidMount: function() {
+  componentDidMount() {
     ensurePositiveDelayProps(this.props);
-  },
 
-  UNSAFE_componentWillReceiveProps: function(nextProps) {
-    ensurePositiveDelayProps(nextProps);
-  },
+    this._touchMixin.componentDidMount();
+  }
+
+  componentDidUpdate(prevProps: Props, prevState: State) {
+    ensurePositiveDelayProps(this.props);
 
-  componentDidUpdate: function(prevProps, prevState) {
     if (this.props.disabled !== prevProps.disabled) {
       this._opacityInactive(250);
     }
-  },
+  }
+
+  componentWillUnmount() {
+    this._touchMixin.componentWillUnmount();
+  }
 
   /**
    * Animate the touchable to a new opacity.
    */
-  setOpacityTo: function(value: number, duration: number) {
+  setOpacityTo(value: number, duration: number) {
     Animated.timing(this.state.anim, {
       toValue: value,
       duration: duration,
       easing: Easing.inOut(Easing.quad),
       useNativeDriver: true,
     }).start();
-  },
+  }
 
   /**
    * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
    * defined on your component.
    */
-  touchableHandleActivePressIn: function(e: PressEvent) {
+  touchableHandleActivePressIn(e: PressEvent) {
     if (e.dispatchConfig.registrationName === 'onResponderGrant') {
       this._opacityActive(0);
     } else {
       this._opacityActive(150);
     }
     this.props.onPressIn && this.props.onPressIn(e);
-  },
+  }
 
-  touchableHandleActivePressOut: function(e: PressEvent) {
+  touchableHandleActivePressOut(e: PressEvent) {
     this._opacityInactive(250);
     this.props.onPressOut && this.props.onPressOut(e);
-  },
+  }
 
-  touchableHandleFocus: function(e: Event) {
+  touchableHandleFocus(e: FocusEvent) {
     if (Platform.isTV) {
       this._opacityActive(150);
     }
     this.props.onFocus && this.props.onFocus(e);
-  },
+  }
 
-  touchableHandleBlur: function(e: Event) {
+  touchableHandleBlur(e: BlurEvent) {
     if (Platform.isTV) {
       this._opacityInactive(250);
     }
     this.props.onBlur && this.props.onBlur(e);
-  },
+  }
 
-  touchableHandlePress: function(e: PressEvent) {
+  touchableHandlePress(e: PressEvent) {
     this.props.onPress && this.props.onPress(e);
-  },
+  }
 
-  touchableHandleLongPress: function(e: PressEvent) {
+  touchableHandleLongPress(e: PressEvent) {
     this.props.onLongPress && this.props.onLongPress(e);
-  },
+  }
 
-  touchableGetPressRectOffset: function() {
+  touchableGetPressRectOffset() {
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
-  },
+  }
 
-  touchableGetHitSlop: function() {
+  touchableGetHitSlop() {
     return this.props.hitSlop;
-  },
+  }
 
-  touchableGetHighlightDelayMS: function() {
+  touchableGetHighlightDelayMS() {
     return this.props.delayPressIn || 0;
-  },
+  }
 
-  touchableGetLongPressDelayMS: function() {
+  touchableGetLongPressDelayMS() {
     return this.props.delayLongPress === 0
       ? 0
       : this.props.delayLongPress || 500;
-  },
+  }
 
-  touchableGetPressOutDelayMS: function() {
+  touchableGetPressOutDelayMS() {
     return this.props.delayPressOut;
-  },
+  }
 
-  _opacityActive: function(duration: number) {
+  _opacityActive(duration: number) {
     this.setOpacityTo(this.props.activeOpacity, duration);
-  },
+  }
 
-  _opacityInactive: function(duration: number) {
+  _opacityInactive(duration: number) {
     this.setOpacityTo(this._getChildStyleOpacityWithDefault(), duration);
-  },
+  }
 
-  _getChildStyleOpacityWithDefault: function() {
+  _getChildStyleOpacityWithDefault(): number {
     const childStyle = flattenStyle(this.props.style) || {};
+
+    // childStyle.opacity could be an AnimatedNode
+    if (typeof childStyle.opacity !== 'number') {
+      return 1;
+    }
+
     return childStyle.opacity == null ? 1 : childStyle.opacity;
-  },
+  }
 
-  render: function() {
+  render() {
     return (
       <Animated.View
         accessible={this.props.accessible !== false}
@@ -324,14 +360,18 @@ const TouchableOpacity = ((createReactClass({
         hasTVPreferredFocus={this.props.hasTVPreferredFocus}
         tvParallaxProperties={this.props.tvParallaxProperties}
         hitSlop={this.props.hitSlop}
-        onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
+        onStartShouldSetResponder={
+          this._touchMixin.touchableHandleStartShouldSetResponder
+        }
         onResponderTerminationRequest={
-          this.touchableHandleResponderTerminationRequest
+          this._touchMixin.touchableHandleResponderTerminationRequest
         }
-        onResponderGrant={this.touchableHandleResponderGrant}
-        onResponderMove={this.touchableHandleResponderMove}
-        onResponderRelease={this.touchableHandleResponderRelease}
-        onResponderTerminate={this.touchableHandleResponderTerminate}>
+        onResponderGrant={this._touchMixin.touchableHandleResponderGrant}
+        onResponderMove={this._touchMixin.touchableHandleResponderMove}
+        onResponderRelease={this._touchMixin.touchableHandleResponderRelease}
+        onResponderTerminate={
+          this._touchMixin.touchableHandleResponderTerminate
+        }>
         {this.props.children}
         {Touchable.renderDebugView({
           color: 'cyan',
@@ -339,7 +379,7 @@ const TouchableOpacity = ((createReactClass({
         })}
       </Animated.View>
     );
-  },
-}): any): React.ComponentType<Props>);
+  }
+}
 
 module.exports = TouchableOpacity;
diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
index a78de287e41e77..3e17ed79066bc7 100755
--- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js
+++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
@@ -10,24 +10,15 @@
 
 'use strict';
 
-const DeprecatedEdgeInsetsPropType = require('DeprecatedEdgeInsetsPropType');
 const React = require('React');
-const PropTypes = require('prop-types');
 const Touchable = require('Touchable');
 const View = require('View');
 
-const createReactClass = require('create-react-class');
 const ensurePositiveDelayProps = require('ensurePositiveDelayProps');
 
-const {
-  DeprecatedAccessibilityComponentTypes,
-  DeprecatedAccessibilityRoles,
-  DeprecatedAccessibilityStates,
-  DeprecatedAccessibilityTraits,
-} = require('DeprecatedViewAccessibility');
-
 import type {SyntheticEvent, LayoutEvent, PressEvent} from 'CoreEventTypes';
 import type {EdgeInsetsProp} from 'EdgeInsetsPropType';
+import type {TouchableState} from 'Touchable';
 import type {
   AccessibilityComponentType,
   AccessibilityRole,
@@ -72,24 +63,91 @@ export type Props = $ReadOnly<{|
   accessibilityStates?: ?AccessibilityStates,
   accessibilityTraits?: ?AccessibilityTraits,
   children?: ?React.Node,
+  /**
+   * Delay in ms, from onPressIn, before onLongPress is called.
+   */
   delayLongPress?: ?number,
+  /**
+   * Delay in ms, from the start of the touch, before onPressIn is called.
+   */
   delayPressIn?: ?number,
+  /**
+   * Delay in ms, from the release of the touch, before onPressOut is called.
+   */
   delayPressOut?: ?number,
+  /**
+   * If true, disable all interactions for this component.
+   */
   disabled?: ?boolean,
+  /**
+   * This defines how far your touch can start away from the button. This is
+   * added to `pressRetentionOffset` when moving off of the button.
+   * ** NOTE **
+   * The touch area never extends past the parent view bounds and the Z-index
+   * of sibling views always takes precedence if a touch hits two overlapping
+   * views.
+   */
   hitSlop?: ?EdgeInsetsProp,
   nativeID?: ?string,
+  /**
+   * When `accessible` is true (which is the default) this may be called when
+   * the OS-specific concept of "blur" occurs, meaning the element lost focus.
+   * Some platforms may not have the concept of blur.
+   */
   onBlur?: ?(e: BlurEvent) => void,
+  /**
+   * When `accessible` is true (which is the default) this may be called when
+   * the OS-specific concept of "focus" occurs. Some platforms may not have
+   * the concept of focus.
+   */
   onFocus?: ?(e: FocusEvent) => void,
+  /**
+   * Invoked on mount and layout changes with
+   *
+   *   `{nativeEvent: {layout: {x, y, width, height}}}`
+   */
   onLayout?: ?(event: LayoutEvent) => mixed,
   onLongPress?: ?(event: PressEvent) => mixed,
+  /**
+   * Called when the touch is released, but not if cancelled (e.g. by a scroll
+   * that steals the responder lock).
+   */
   onPress?: ?(event: PressEvent) => mixed,
+  /**
+   * Called as soon as the touchable element is pressed and invoked even before onPress.
+   * This can be useful when making network requests.
+   */
   onPressIn?: ?(event: PressEvent) => mixed,
+  /**
+   * Called as soon as the touch is released even before onPress.
+   */
   onPressOut?: ?(event: PressEvent) => mixed,
+  /**
+   * When the scroll view is disabled, this defines how far your touch may
+   * move off of the button, before deactivating the button. Once deactivated,
+   * try moving it back and you'll see that the button is once again
+   * reactivated! Move it back and forth several times while the scroll view
+   * is disabled. Ensure you pass in a constant to reduce memory allocations.
+   */
   pressRetentionOffset?: ?EdgeInsetsProp,
   rejectResponderTermination?: ?boolean,
   testID?: ?string,
 |}>;
 
+function createTouchMixin(
+  node: React.ElementRef<typeof TouchableWithoutFeedback>,
+): typeof Touchable.Mixin {
+  const touchMixin = {...Touchable.Mixin};
+
+  for (const key in touchMixin) {
+    if (typeof touchMixin[key] === 'function') {
+      touchMixin[key] = touchMixin[key].bind(node);
+    }
+  }
+
+  return touchMixin;
+}
+
 /**
  * Do not use unless you have a very good reason. All elements that
  * respond to press should have a visual feedback when touched.
@@ -97,155 +155,118 @@ export type Props = $ReadOnly<{|
  * TouchableWithoutFeedback supports only one child.
  * If you wish to have several child components, wrap them in a View.
  */
-const TouchableWithoutFeedback = ((createReactClass({
-  displayName: 'TouchableWithoutFeedback',
-  mixins: [Touchable.Mixin],
-
-  propTypes: {
-    accessible: PropTypes.bool,
-    accessibilityLabel: PropTypes.node,
-    accessibilityHint: PropTypes.string,
-    accessibilityComponentType: PropTypes.oneOf(
-      DeprecatedAccessibilityComponentTypes,
-    ),
-    accessibilityIgnoresInvertColors: PropTypes.bool,
-    accessibilityRole: PropTypes.oneOf(DeprecatedAccessibilityRoles),
-    accessibilityStates: PropTypes.arrayOf(
-      PropTypes.oneOf(DeprecatedAccessibilityStates),
-    ),
-    accessibilityTraits: PropTypes.oneOfType([
-      PropTypes.oneOf(DeprecatedAccessibilityTraits),
-      PropTypes.arrayOf(PropTypes.oneOf(DeprecatedAccessibilityTraits)),
-    ]),
-    /**
-     * When `accessible` is true (which is the default) this may be called when
-     * the OS-specific concept of "focus" occurs. Some platforms may not have
-     * the concept of focus.
-     */
-    onFocus: PropTypes.func,
-    /**
-     * When `accessible` is true (which is the default) this may be called when
-     * the OS-specific concept of "blur" occurs, meaning the element lost focus.
-     * Some platforms may not have the concept of blur.
-     */
-    onBlur: PropTypes.func,
-    /**
-     * If true, disable all interactions for this component.
-     */
-    disabled: PropTypes.bool,
-    /**
-     * Called when the touch is released, but not if cancelled (e.g. by a scroll
-     * that steals the responder lock).
-     */
-    onPress: PropTypes.func,
-    /**
-     * Called as soon as the touchable element is pressed and invoked even before onPress.
-     * This can be useful when making network requests.
-     */
-    onPressIn: PropTypes.func,
-    /**
-     * Called as soon as the touch is released even before onPress.
-     */
-    onPressOut: PropTypes.func,
-    /**
-     * Invoked on mount and layout changes with
-     *
-     *   `{nativeEvent: {layout: {x, y, width, height}}}`
-     */
-    onLayout: PropTypes.func,
+class TouchableWithoutFeedback extends React.Component<Props, TouchableState> {
+  /**
+   * Part 1: Removing Touchable.Mixin:
+   *
+   * 1. Mixin methods should be flow typed. That's why we create a
+   *    copy of Touchable.Mixin and attach it to this._touchMixin.
+   *    Otherwise, we'd have to manually declare each method on the component
+   *    class and assign it a flow type.
+   * 2. Mixin methods can call component methods, and access the component's
+   *    props and state. So, we need to bind all mixin methods to the
+   *    component instance.
+   * 3. Continued...
+   */
+  _touchMixin: typeof Touchable.Mixin = createTouchMixin(this);
 
-    onLongPress: PropTypes.func,
+  _isMounted: boolean;
 
-    nativeID: PropTypes.string,
-    testID: PropTypes.string,
+  constructor(props: Props) {
+    super(props);
 
     /**
-     * Delay in ms, from the start of the touch, before onPressIn is called.
-     */
-    delayPressIn: PropTypes.number,
-    /**
-     * Delay in ms, from the release of the touch, before onPressOut is called.
-     */
-    delayPressOut: PropTypes.number,
-    /**
-     * Delay in ms, from onPressIn, before onLongPress is called.
-     */
-    delayLongPress: PropTypes.number,
-    /**
-     * When the scroll view is disabled, this defines how far your touch may
-     * move off of the button, before deactivating the button. Once deactivated,
-     * try moving it back and you'll see that the button is once again
-     * reactivated! Move it back and forth several times while the scroll view
-     * is disabled. Ensure you pass in a constant to reduce memory allocations.
+     * Part 2: Removing Touchable.Mixin
+     *
+     * 3. Mixin methods access other mixin methods via dynamic dispatch using
+     *    this. Since mixin methods are bound to the component instance, we need
+     *    to copy all mixin methods to the component instance.
      */
-    pressRetentionOffset: DeprecatedEdgeInsetsPropType,
+    const touchMixin = Touchable.Mixin;
+    for (const key in touchMixin) {
+      if (
+        typeof touchMixin[key] === 'function' &&
+        (key.startsWith('_') || key.startsWith('touchable'))
+      ) {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key].bind(this);
+      }
+    }
+
     /**
-     * This defines how far your touch can start away from the button. This is
-     * added to `pressRetentionOffset` when moving off of the button.
-     * ** NOTE **
-     * The touch area never extends past the parent view bounds and the Z-index
-     * of sibling views always takes precedence if a touch hits two overlapping
-     * views.
+     * Part 3: Removing Touchable.Mixin
+     *
+     * 4. Mixins can initialize properties and use properties on the component
+     *    instance.
      */
-    hitSlop: DeprecatedEdgeInsetsPropType,
-  },
+    Object.keys(touchMixin)
+      .filter(key => typeof touchMixin[key] !== 'function')
+      .forEach(key => {
+        // $FlowFixMe - dynamically adding properties to a class
+        (this: any)[key] = touchMixin[key];
+      });
+
+    this.state = this._touchMixin.touchableGetInitialState();
+  }
+
+  componentDidMount() {
+    ensurePositiveDelayProps(this.props);
 
-  getInitialState: function() {
-    return this.touchableGetInitialState();
-  },
+    this._touchMixin.componentDidMount();
+  }
 
-  componentDidMount: function() {
+  componentDidUpdate(prevProps: Props, prevState: TouchableState) {
     ensurePositiveDelayProps(this.props);
-  },
+  }
 
-  UNSAFE_componentWillReceiveProps: function(nextProps: Object) {
-    ensurePositiveDelayProps(nextProps);
-  },
+  componentWillUnmount() {
+    this._touchMixin.componentWillUnmount();
+  }
 
   /**
    * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
    * defined on your component.
    */
-  touchableHandlePress: function(e: PressEvent) {
+  touchableHandlePress(e: PressEvent) {
     this.props.onPress && this.props.onPress(e);
-  },
+  }
 
-  touchableHandleActivePressIn: function(e: PressEvent) {
+  touchableHandleActivePressIn(e: PressEvent) {
     this.props.onPressIn && this.props.onPressIn(e);
-  },
+  }
 
-  touchableHandleActivePressOut: function(e: PressEvent) {
+  touchableHandleActivePressOut(e: PressEvent) {
     this.props.onPressOut && this.props.onPressOut(e);
-  },
+  }
 
-  touchableHandleLongPress: function(e: PressEvent) {
+  touchableHandleLongPress(e: PressEvent) {
     this.props.onLongPress && this.props.onLongPress(e);
-  },
+  }
 
-  touchableGetPressRectOffset: function(): typeof PRESS_RETENTION_OFFSET {
+  touchableGetPressRectOffset(): EdgeInsetsProp {
     // $FlowFixMe Invalid prop usage
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
-  },
+  }
 
-  touchableGetHitSlop: function(): ?Object {
+  touchableGetHitSlop(): ?EdgeInsetsProp {
     return this.props.hitSlop;
-  },
+  }
 
-  touchableGetHighlightDelayMS: function(): number {
+  touchableGetHighlightDelayMS(): number {
     return this.props.delayPressIn || 0;
-  },
+  }
 
-  touchableGetLongPressDelayMS: function(): number {
+  touchableGetLongPressDelayMS(): number {
     return this.props.delayLongPress === 0
       ? 0
       : this.props.delayLongPress || 500;
-  },
+  }
 
-  touchableGetPressOutDelayMS: function(): number {
+  touchableGetPressOutDelayMS(): number {
     return this.props.delayPressOut || 0;
-  },
+  }
 
-  render: function(): React.Element<any> {
+  render(): React.Element<any> {
     // Note(avik): remove dynamic typecast once Flow has been upgraded
     // $FlowFixMe(>=0.41.0)
     const child = React.Children.only(this.props.children);
@@ -267,16 +288,17 @@ const TouchableWithoutFeedback = ((createReactClass({
     return (React: any).cloneElement(child, {
       ...overrides,
       accessible: this.props.accessible !== false,
-      onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
-      onResponderTerminationRequest: this
+      onStartShouldSetResponder: this._touchMixin
+        .touchableHandleStartShouldSetResponder,
+      onResponderTerminationRequest: this._touchMixin
         .touchableHandleResponderTerminationRequest,
-      onResponderGrant: this.touchableHandleResponderGrant,
-      onResponderMove: this.touchableHandleResponderMove,
-      onResponderRelease: this.touchableHandleResponderRelease,
-      onResponderTerminate: this.touchableHandleResponderTerminate,
+      onResponderGrant: this._touchMixin.touchableHandleResponderGrant,
+      onResponderMove: this._touchMixin.touchableHandleResponderMove,
+      onResponderRelease: this._touchMixin.touchableHandleResponderRelease,
+      onResponderTerminate: this._touchMixin.touchableHandleResponderTerminate,
       children,
     });
-  },
-}): any): React.ComponentType<Props>);
+  }
+}
 
 module.exports = TouchableWithoutFeedback;
diff --git a/RNTester/js/StrictModeExample.js b/RNTester/js/StrictModeExample.js
index 1ca3964d76c263..83e1dfca4fdcb7 100644
--- a/RNTester/js/StrictModeExample.js
+++ b/RNTester/js/StrictModeExample.js
@@ -13,23 +13,73 @@
 const React = require('react');
 const {StrictMode} = React;
 const ReactNative = require('react-native');
-const {ScrollView, Text} = ReactNative;
+const {
+  ScrollView,
+  Text,
+  TouchableHighlight,
+  TouchableNativeFeedback,
+  TouchableOpacity,
+  TouchableWithoutFeedback,
+  View,
+} = ReactNative;
+const TouchableBounce = require('TouchableBounce');
 
 type Props = $ReadOnly<{||}>;
 type State = {|result: string|};
 
-const componentsToTest = [ScrollView];
+const componentsToTest = [
+  [ScrollView, {}],
+  [
+    TouchableBounce,
+    {
+      onPress: () => console.warn('[press]'),
+    },
+  ],
+  [
+    TouchableHighlight,
+    {
+      onPress: () => console.warn('[press]'),
+    },
+  ],
+  [
+    // Caveat: Contains ReactNative.findNodeHandle which is not allowed in strict mode
+    TouchableNativeFeedback,
+    {
+      onPress: () => console.warn('[press]'),
+      background: TouchableNativeFeedback.Ripple('rgba(0, 0, 255, 0.4)', true),
+      children: (
+        <View style={{height: 100, backgroundColor: 'red'}}>
+          <Text style={{margin: 30}}>I am a TouchableNativeFeedback</Text>
+        </View>
+      ),
+    },
+  ],
+  [
+    TouchableOpacity,
+    {
+      onPress: () => console.warn('[press]'),
+    },
+  ],
+  [
+    TouchableWithoutFeedback,
+    {
+      onPress: () => console.warn('[press]'),
+    },
+  ],
+];
 
 class StrictModeExample extends React.Component<Props, State> {
   render() {
     return (
-      <StrictMode>
-        {componentsToTest.map(Component => (
-          <Component key={Component.displayName}>
-            <Text>{Component.displayName}</Text>
-          </Component>
-        ))}
-      </StrictMode>
+      <View>
+        <StrictMode>
+          {componentsToTest.map(([Component, props]) => (
+            <Component key={Component.displayName} {...props}>
+              {props.children || <Text>I am a {Component.displayName}</Text>}
+            </Component>
+          ))}
+        </StrictMode>
+      </View>
     );
   }
 }