Repository: mui/material-ui (TypeScript)
Date: 2019-05-29 21:06:14+00:00
Task type: Bug Fix

Issue:
Swipeable drawer not working on ios
<!--- Provide a general summary of the issue in the Title above -->
I can't get the swipeable drawer to open on swipe either on an actual iphone or using chrome dev tools with an iphone model selected. The main difference between simulation iphone and android seems to be that ios has no swiping area div
<!--
    Thank you very much for contributing to Material-UI by creating an issue! ❤️
    To avoid duplicate issues we ask you to check off the following list.
-->

<!-- Checked checkbox should look like this: [x] -->
- [x] This is not a v0.x issue. <!-- (v0.x is no longer maintained) -->
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Expected Behavior 🤔
<!---
    Describe what should happen.
-->

The drawer should open on swipe

## Current Behavior 😯
<!---
    Describe what happens instead of the expected behavior.
-->
Drawer is not opening
## Steps to Reproduce 🕹
<!---
    Provide a link to a live example (you can use codesandbox.io) and an unambiguous set of steps to reproduce this bug.
    Include code to reproduce, if relevant (which it most likely is).

    This codesandbox.io template _may_ be a good starting point:
    https://codesandbox.io/s/github/mui-org/material-ui/tree/next/examples/create-react-app

    If you're using typescript a better starting point would be
    https://codesandbox.io/s/github/mui-org/material-ui/tree/next/examples/create-react-app-with-typescript

    If YOU DO NOT take time to provide a codesandbox.io reproduction, should the COMMUNITY take time to help you?

-->
Did a quick demo, try to load the page with dev tools with simulated ios device  or an iphone.
Can also not use the drawers on the Drawer demo when simulation an iphone

Link:
1. https://codesandbox.io/s/81j1xzoyw8
2 .https://material-ui.com/demos/drawers/

## Your Environment 🌎
<!---
    Include as many relevant details about the environment with which you experienced the bug.
    If you encounter issues with typescript please include version and tsconfig.
-->

| Tech         | Version |
|--------------|---------|
| Material-UI  | v3.9.2   |
| React          |  v16.5.2  |
| Browser      |  Chrome |




Issue discussion:
This is expected behavior. The reasons are outlined under https://material-ui.com/demos/drawers/#swipeable-temporary-drawer.

The only thing I recognized is that the full page is swiping once the drawer is open and not just the drawer. Maybe add a preventDefault when swiping? /cc @oliviertassinari 
> I can't get the swipeable drawer to open on swipe

@Najarana How did you try to open it?

> the full page is swiping once the drawer is open and not just the drawer

@eps1lon Should we go one step further (on top of disabling the discovery area) by disabling the swipe to open feature of the Drawer on iOS to account for the swipe to go back feature of iOS? I don't think that prevent default can help (but I can be wrong). 
@oliviertassinari This is happening when closing it with swiping. Opening only works by clicking the button (which is what is expected I thought).
This is new for me. I was only considering the opening case. Do you have a visual illustration? 
> This is new for me. I was only considering the opening case. Do you have a visual illustration?

@oliviertassinari 
![mui-swipe-drawer-ios](https://user-images.githubusercontent.com/12292047/57697315-367c7500-7653-11e9-9c31-5f4367e05a97.gif)

browserstack iPhone XS using safari
I saw similar behaviour whilst testing natively 
I can reproduce this with an iPhone Xr, iOS 12.2 simulator on my MacBook Air. The vertical scroll behavior is wrong!
https://imgur.com/gallery/50Ricxu filmed on my phone
> Should we go one step further (on top of disabling the discovery area) by disabling the swipe to open feature of the Drawer on iOS to account for the swipe to go back feature of iOS?

@oliviertassinari This would only for left/right menu, not for bottom/top, wouldn't it? 🤔 

Unfortunately, I don't have an iPhone to test this on anymore. 😢 
So swipeable drawer only works on top and bottom swipe for iphones? 
I thought the disableDiscovery prop was meant to fix it. Seems I was just confused though it seems like you found some other issues. 
@Najarana I'm sorry, I think that we have diverged from your initial point.
In your case, it should be fine. The behavior you described is expected.

Solution patch:
diff --git a/packages/material-ui/src/SwipeableDrawer/SwipeableDrawer.js b/packages/material-ui/src/SwipeableDrawer/SwipeableDrawer.js
--- a/packages/material-ui/src/SwipeableDrawer/SwipeableDrawer.js
+++ b/packages/material-ui/src/SwipeableDrawer/SwipeableDrawer.js
@@ -1,5 +1,3 @@
-/* eslint-disable consistent-this */
-
 import React from 'react';
 import PropTypes from 'prop-types';
 import ReactDOM from 'react-dom';
@@ -9,7 +7,6 @@ import { duration } from '../styles/transitions';
 import withTheme from '../styles/withTheme';
 import { getTransitionProps } from '../transitions/utils';
 import NoSsr from '../NoSsr';
-import withForwardedRef from '../utils/withForwardedRef';
 import SwipeArea from './SwipeArea';
 
 // This value is closed to what browsers are using internally to
@@ -26,388 +23,401 @@ export function reset() {
   nodeThatClaimedTheSwipe = null;
 }
 
-class SwipeableDrawer extends React.Component {
-  state = {};
-
-  isSwiping = null;
-
-  swipeAreaRef = React.createRef();
+function calculateCurrentX(anchor, touches) {
+  return anchor === 'right' ? document.body.offsetWidth - touches[0].pageX : touches[0].pageX;
+}
 
-  paperRef = null;
+function calculateCurrentY(anchor, touches) {
+  return anchor === 'bottom' ? window.innerHeight - touches[0].clientY : touches[0].clientY;
+}
 
-  componentDidMount() {
-    if (this.props.variant === 'temporary') {
-      this.listenTouchStart();
-    }
-  }
+function getMaxTranslate(horizontalSwipe, paperInstance) {
+  return horizontalSwipe ? paperInstance.clientWidth : paperInstance.clientHeight;
+}
 
-  componentDidUpdate(prevProps) {
-    const variant = this.props.variant;
-    const prevVariant = prevProps.variant;
+function getTranslate(currentTranslate, startLocation, open, maxTranslate) {
+  return Math.min(
+    Math.max(
+      open ? startLocation - currentTranslate : maxTranslate + startLocation - currentTranslate,
+      0,
+    ),
+    maxTranslate,
+  );
+}
 
-    if (variant !== prevVariant) {
-      if (variant === 'temporary') {
-        this.listenTouchStart();
-      } else if (prevVariant === 'temporary') {
-        this.removeTouchStart();
+const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
+
+const SwipeableDrawer = React.forwardRef(function SwipeableDrawer(props, ref) {
+  const {
+    anchor,
+    disableBackdropTransition,
+    disableDiscovery,
+    disableSwipeToOpen,
+    hideBackdrop,
+    hysteresis,
+    minFlingVelocity,
+    ModalProps: { BackdropProps, ...ModalPropsProp } = {},
+    onClose,
+    onOpen,
+    open,
+    PaperProps = {},
+    SwipeAreaProps,
+    swipeAreaWidth,
+    theme,
+    transitionDuration,
+    variant,
+    ...other
+  } = props;
+
+  const [maybeSwiping, setMaybeSwiping] = React.useState(false);
+  const swipeInstance = React.useRef({
+    isSwiping: null,
+  });
+  const swipeAreaRef = React.useRef();
+  const backdropRef = React.useRef();
+  const paperRef = React.useRef();
+
+  const touchDetected = React.useRef(false);
+  const openRef = React.useRef(open);
+
+  // Use a ref so the open value used is always up to date inside useCallback.
+  useEnhancedEffect(() => {
+    openRef.current = open;
+  }, [open]);
+
+  const setPosition = React.useCallback(
+    (translate, options = {}) => {
+      const { mode = null, changeTransition = true } = options;
+
+      const anchorRtl = getAnchor(theme, anchor);
+      const rtlTranslateMultiplier = ['right', 'bottom'].indexOf(anchorRtl) !== -1 ? 1 : -1;
+      const horizontalSwipe = isHorizontal(anchor);
+
+      const transform = horizontalSwipe
+        ? `translate(${rtlTranslateMultiplier * translate}px, 0)`
+        : `translate(0, ${rtlTranslateMultiplier * translate}px)`;
+      const drawerStyle = paperRef.current.style;
+      drawerStyle.webkitTransform = transform;
+      drawerStyle.transform = transform;
+
+      let transition = '';
+
+      if (mode) {
+        transition = theme.transitions.create(
+          'all',
+          getTransitionProps(
+            {
+              timeout: transitionDuration,
+            },
+            {
+              mode,
+            },
+          ),
+        );
       }
-    }
-  }
-
-  componentWillUnmount() {
-    this.removeTouchStart();
-    this.removeBodyTouchListeners();
 
-    // We need to release the lock.
-    if (nodeThatClaimedTheSwipe === this) {
-      nodeThatClaimedTheSwipe = null;
-    }
-  }
+      if (changeTransition) {
+        drawerStyle.webkitTransition = transition;
+        drawerStyle.transition = transition;
+      }
 
-  static getDerivedStateFromProps(nextProps, prevState) {
-    if (typeof prevState.maybeSwiping === 'undefined') {
-      return {
-        maybeSwiping: false,
-        open: nextProps.open,
-      };
-    }
+      if (!disableBackdropTransition && !hideBackdrop) {
+        const backdropStyle = backdropRef.current.style;
+        backdropStyle.opacity = 1 - translate / getMaxTranslate(horizontalSwipe, paperRef.current);
 
-    if (!nextProps.open && prevState.open) {
-      return {
-        maybeSwiping: false,
-        open: nextProps.open,
-      };
-    }
+        if (changeTransition) {
+          backdropStyle.webkitTransition = transition;
+          backdropStyle.transition = transition;
+        }
+      }
+    },
+    [anchor, disableBackdropTransition, hideBackdrop, theme, transitionDuration],
+  );
 
-    return {
-      open: nextProps.open,
-    };
-  }
-
-  getMaxTranslate() {
-    return isHorizontal(this.props.anchor) ? this.paperRef.clientWidth : this.paperRef.clientHeight;
-  }
-
-  getTranslate(current) {
-    const start = isHorizontal(this.props.anchor) ? this.startX : this.startY;
-    return Math.min(
-      Math.max(this.props.open ? start - current : this.getMaxTranslate() + start - current, 0),
-      this.getMaxTranslate(),
-    );
-  }
-
-  setPosition(translate, options = {}) {
-    const { mode = null, changeTransition = true } = options;
-
-    const anchor = getAnchor(this.props.theme, this.props.anchor);
-    const rtlTranslateMultiplier = ['right', 'bottom'].indexOf(anchor) !== -1 ? 1 : -1;
-    const transform = isHorizontal(this.props.anchor)
-      ? `translate(${rtlTranslateMultiplier * translate}px, 0)`
-      : `translate(0, ${rtlTranslateMultiplier * translate}px)`;
-    const drawerStyle = this.paperRef.style;
-    drawerStyle.webkitTransform = transform;
-    drawerStyle.transform = transform;
-
-    let transition = '';
-
-    if (mode) {
-      transition = this.props.theme.transitions.create(
-        'all',
-        getTransitionProps(
-          {
-            timeout: this.props.transitionDuration,
-          },
-          {
-            mode,
-          },
-        ),
-      );
-    }
+  const handleBodyTouchEnd = React.useCallback(
+    event => {
+      if (!touchDetected.current) {
+        return;
+      }
+      nodeThatClaimedTheSwipe = null;
+      touchDetected.current = false;
+      setMaybeSwiping(false);
 
-    if (changeTransition) {
-      drawerStyle.webkitTransition = transition;
-      drawerStyle.transition = transition;
-    }
+      // The swipe wasn't started.
+      if (!swipeInstance.current.isSwiping) {
+        swipeInstance.current.isSwiping = null;
+        return;
+      }
 
-    if (!this.props.disableBackdropTransition && !this.props.hideBackdrop) {
-      const backdropStyle = this.backdropRef.style;
-      backdropStyle.opacity = 1 - translate / this.getMaxTranslate();
+      swipeInstance.current.isSwiping = null;
 
-      if (changeTransition) {
-        backdropStyle.webkitTransition = transition;
-        backdropStyle.transition = transition;
+      const anchorRtl = getAnchor(theme, anchor);
+      const horizontal = isHorizontal(anchor);
+      let current;
+      if (horizontal) {
+        current = calculateCurrentX(anchorRtl, event.changedTouches);
+      } else {
+        current = calculateCurrentY(anchorRtl, event.changedTouches);
       }
-    }
-  }
-
-  handleBodyTouchStart = event => {
-    // We are not supposed to handle this touch move.
-    if (nodeThatClaimedTheSwipe !== null && nodeThatClaimedTheSwipe !== this) {
-      return;
-    }
 
-    const { disableDiscovery, disableSwipeToOpen, open, swipeAreaWidth } = this.props;
-    const anchor = getAnchor(this.props.theme, this.props.anchor);
-    const currentX =
-      anchor === 'right'
-        ? document.body.offsetWidth - event.touches[0].pageX
-        : event.touches[0].pageX;
-    const currentY =
-      anchor === 'bottom'
-        ? window.innerHeight - event.touches[0].clientY
-        : event.touches[0].clientY;
+      const startLocation = horizontal
+        ? swipeInstance.current.startX
+        : swipeInstance.current.startY;
+      const maxTranslate = getMaxTranslate(horizontal, paperRef.current);
+      const currentTranslate = getTranslate(current, startLocation, openRef.current, maxTranslate);
+      const translateRatio = currentTranslate / maxTranslate;
+
+      if (openRef.current) {
+        if (swipeInstance.current.velocity > minFlingVelocity || translateRatio > hysteresis) {
+          onClose();
+        } else {
+          // Reset the position, the swipe was aborted.
+          setPosition(0, {
+            mode: 'exit',
+          });
+        }
 
-    if (!open) {
-      if (disableSwipeToOpen || event.target !== this.swipeAreaRef.current) {
         return;
       }
-      if (isHorizontal(this.props.anchor)) {
-        if (currentX > swipeAreaWidth) {
-          return;
-        }
-      } else if (currentY > swipeAreaWidth) {
+
+      if (swipeInstance.current.velocity < -minFlingVelocity || 1 - translateRatio > hysteresis) {
+        onOpen();
+      } else {
+        // Reset the position, the swipe was aborted.
+        setPosition(getMaxTranslate(horizontal, paperRef.current), {
+          mode: 'enter',
+        });
+      }
+    },
+    [anchor, hysteresis, minFlingVelocity, onClose, onOpen, setPosition, theme],
+  );
+
+  const handleBodyTouchMove = React.useCallback(
+    event => {
+      // the ref may be null when a parent component updates while swiping
+      if (!paperRef.current || !touchDetected.current) {
         return;
       }
-    }
 
-    nodeThatClaimedTheSwipe = this;
-    this.startX = currentX;
-    this.startY = currentY;
+      const anchorRtl = getAnchor(theme, anchor);
+      const horizontalSwipe = isHorizontal(anchor);
 
-    this.setState({ maybeSwiping: true });
-    if (!open && this.paperRef) {
-      // The ref may be null when a parent component updates while swiping.
-      this.setPosition(this.getMaxTranslate() + (disableDiscovery ? 20 : -swipeAreaWidth), {
-        changeTransition: false,
-      });
-    }
+      const currentX = calculateCurrentX(anchorRtl, event.touches);
+      const currentY = calculateCurrentY(anchorRtl, event.touches);
 
-    this.velocity = 0;
-    this.lastTime = null;
-    this.lastTranslate = null;
-
-    document.body.addEventListener('touchmove', this.handleBodyTouchMove, { passive: false });
-    document.body.addEventListener('touchend', this.handleBodyTouchEnd);
-    // https://plus.google.com/+PaulIrish/posts/KTwfn1Y2238
-    document.body.addEventListener('touchcancel', this.handleBodyTouchEnd);
-  };
-
-  handleBodyTouchMove = event => {
-    // the ref may be null when a parent component updates while swiping
-    if (!this.paperRef) return;
-
-    const anchor = getAnchor(this.props.theme, this.props.anchor);
-    const horizontalSwipe = isHorizontal(this.props.anchor);
-
-    const currentX =
-      anchor === 'right'
-        ? document.body.offsetWidth - event.touches[0].pageX
-        : event.touches[0].pageX;
-    const currentY =
-      anchor === 'bottom'
-        ? window.innerHeight - event.touches[0].clientY
-        : event.touches[0].clientY;
-
-    // We don't know yet.
-    if (this.isSwiping == null) {
-      const dx = Math.abs(currentX - this.startX);
-      const dy = Math.abs(currentY - this.startY);
-
-      // We are likely to be swiping, let's prevent the scroll event on iOS.
-      if (dx > dy) {
-        event.preventDefault();
-      }
+      // We don't know yet.
+      if (swipeInstance.current.isSwiping == null) {
+        const dx = Math.abs(currentX - swipeInstance.current.startX);
+        const dy = Math.abs(currentY - swipeInstance.current.startY);
 
-      const isSwiping = horizontalSwipe
-        ? dx > dy && dx > UNCERTAINTY_THRESHOLD
-        : dy > dx && dy > UNCERTAINTY_THRESHOLD;
-
-      if (
-        isSwiping === true ||
-        (horizontalSwipe ? dy > UNCERTAINTY_THRESHOLD : dx > UNCERTAINTY_THRESHOLD)
-      ) {
-        this.isSwiping = isSwiping;
-        if (!isSwiping) {
-          this.handleBodyTouchEnd(event);
-          return;
+        // We are likely to be swiping, let's prevent the scroll event on iOS.
+        if (dx > dy) {
+          if (event.cancelable) {
+            event.preventDefault();
+          }
         }
 
-        // Shift the starting point.
-        this.startX = currentX;
-        this.startY = currentY;
+        const definitelySwiping = horizontalSwipe
+          ? dx > dy && dx > UNCERTAINTY_THRESHOLD
+          : dy > dx && dy > UNCERTAINTY_THRESHOLD;
+
+        if (
+          definitelySwiping === true ||
+          (horizontalSwipe ? dy > UNCERTAINTY_THRESHOLD : dx > UNCERTAINTY_THRESHOLD)
+        ) {
+          swipeInstance.current.isSwiping = definitelySwiping;
+          if (!definitelySwiping) {
+            handleBodyTouchEnd(event);
+            return;
+          }
 
-        // Compensate for the part of the drawer displayed on touch start.
-        if (!this.props.disableDiscovery && !this.props.open) {
-          if (horizontalSwipe) {
-            this.startX -= this.props.swipeAreaWidth;
-          } else {
-            this.startY -= this.props.swipeAreaWidth;
+          // Shift the starting point.
+          swipeInstance.current.startX = currentX;
+          swipeInstance.current.startY = currentY;
+
+          // Compensate for the part of the drawer displayed on touch start.
+          if (!disableDiscovery && !openRef.current) {
+            if (horizontalSwipe) {
+              swipeInstance.current.startX -= swipeAreaWidth;
+            } else {
+              swipeInstance.current.startY -= swipeAreaWidth;
+            }
           }
         }
       }
-    }
 
-    if (!this.isSwiping) {
-      return;
-    }
-
-    const translate = this.getTranslate(horizontalSwipe ? currentX : currentY);
+      if (!swipeInstance.current.isSwiping) {
+        return;
+      }
+      const startLocation = horizontalSwipe
+        ? swipeInstance.current.startX
+        : swipeInstance.current.startY;
+      const maxTranslate = getMaxTranslate(horizontalSwipe, paperRef.current);
+
+      const translate = getTranslate(
+        horizontalSwipe ? currentX : currentY,
+        startLocation,
+        openRef.current,
+        maxTranslate,
+      );
 
-    if (this.lastTranslate === null) {
-      this.lastTranslate = translate;
-      this.lastTime = performance.now() + 1;
-    }
+      if (swipeInstance.current.lastTranslate === null) {
+        swipeInstance.current.lastTranslate = translate;
+        swipeInstance.current.lastTime = performance.now() + 1;
+      }
 
-    const velocity = ((translate - this.lastTranslate) / (performance.now() - this.lastTime)) * 1e3;
+      const velocity =
+        ((translate - swipeInstance.current.lastTranslate) /
+          (performance.now() - swipeInstance.current.lastTime)) *
+        1e3;
 
-    // Low Pass filter.
-    this.velocity = this.velocity * 0.4 + velocity * 0.6;
+      // Low Pass filter.
+      swipeInstance.current.velocity = swipeInstance.current.velocity * 0.4 + velocity * 0.6;
 
-    this.lastTranslate = translate;
-    this.lastTime = performance.now();
+      swipeInstance.current.lastTranslate = translate;
+      swipeInstance.current.lastTime = performance.now();
 
-    // We are swiping, let's prevent the scroll event on iOS.
-    event.preventDefault();
-    this.setPosition(translate);
-  };
+      // We are swiping, let's prevent the scroll event on iOS.
+      if (event.cancelable) {
+        event.preventDefault();
+      }
+      setPosition(translate);
+    },
+    [setPosition, handleBodyTouchEnd, anchor, disableDiscovery, swipeAreaWidth, theme],
+  );
+
+  const handleBodyTouchStart = React.useCallback(
+    event => {
+      // We are not supposed to handle this touch move.
+      if (nodeThatClaimedTheSwipe !== null && nodeThatClaimedTheSwipe !== swipeInstance.current) {
+        return;
+      }
 
-  handleBodyTouchEnd = event => {
-    nodeThatClaimedTheSwipe = null;
-    this.removeBodyTouchListeners();
-    this.setState({ maybeSwiping: false });
+      const anchorRtl = getAnchor(theme, anchor);
+      const horizontalSwipe = isHorizontal(anchor);
 
-    // The swipe wasn't started.
-    if (!this.isSwiping) {
-      this.isSwiping = null;
-      return;
-    }
+      const currentX = calculateCurrentX(anchorRtl, event.touches);
+      const currentY = calculateCurrentY(anchorRtl, event.touches);
 
-    this.isSwiping = null;
-
-    const anchor = getAnchor(this.props.theme, this.props.anchor);
-    let current;
-    if (isHorizontal(this.props.anchor)) {
-      current =
-        anchor === 'right'
-          ? document.body.offsetWidth - event.changedTouches[0].pageX
-          : event.changedTouches[0].pageX;
-    } else {
-      current =
-        anchor === 'bottom'
-          ? window.innerHeight - event.changedTouches[0].clientY
-          : event.changedTouches[0].clientY;
-    }
+      if (!openRef.current) {
+        if (disableSwipeToOpen || event.target !== swipeAreaRef.current) {
+          return;
+        }
+        if (horizontalSwipe) {
+          if (currentX > swipeAreaWidth) {
+            return;
+          }
+        } else if (currentY > swipeAreaWidth) {
+          return;
+        }
+      }
 
-    const translateRatio = this.getTranslate(current) / this.getMaxTranslate();
+      nodeThatClaimedTheSwipe = swipeInstance.current;
+      swipeInstance.current.startX = currentX;
+      swipeInstance.current.startY = currentY;
 
-    if (this.props.open) {
-      if (this.velocity > this.props.minFlingVelocity || translateRatio > this.props.hysteresis) {
-        this.props.onClose();
-      } else {
-        // Reset the position, the swipe was aborted.
-        this.setPosition(0, {
-          mode: 'exit',
-        });
+      setMaybeSwiping(true);
+      if (!openRef.current && paperRef.current) {
+        // The ref may be null when a parent component updates while swiping.
+        setPosition(
+          getMaxTranslate(horizontalSwipe, paperRef.current) +
+            (disableDiscovery ? 20 : -swipeAreaWidth),
+          {
+            changeTransition: false,
+          },
+        );
       }
 
-      return;
+      swipeInstance.current.velocity = 0;
+      swipeInstance.current.lastTime = null;
+      swipeInstance.current.lastTranslate = null;
+
+      touchDetected.current = true;
+    },
+    [setPosition, anchor, disableDiscovery, disableSwipeToOpen, swipeAreaWidth, theme],
+  );
+
+  React.useEffect(() => {
+    if (variant === 'temporary') {
+      document.body.addEventListener('touchstart', handleBodyTouchStart);
+      document.body.addEventListener('touchmove', handleBodyTouchMove, { passive: false });
+      document.body.addEventListener('touchend', handleBodyTouchEnd);
+
+      return () => {
+        document.body.removeEventListener('touchstart', handleBodyTouchStart);
+        document.body.removeEventListener('touchmove', handleBodyTouchMove, { passive: false });
+        document.body.removeEventListener('touchend', handleBodyTouchEnd);
+      };
     }
 
-    if (
-      this.velocity < -this.props.minFlingVelocity ||
-      1 - translateRatio > this.props.hysteresis
-    ) {
-      this.props.onOpen();
-    } else {
-      // Reset the position, the swipe was aborted.
-      this.setPosition(this.getMaxTranslate(), {
-        mode: 'enter',
-      });
+    return undefined;
+  }, [variant, handleBodyTouchStart, handleBodyTouchMove, handleBodyTouchEnd]);
+
+  React.useEffect(
+    () => () => {
+      // We need to release the lock.
+      if (nodeThatClaimedTheSwipe === swipeInstance.current) {
+        nodeThatClaimedTheSwipe = null;
+      }
+    },
+    [],
+  );
+
+  React.useEffect(() => {
+    if (!open) {
+      setMaybeSwiping(false);
     }
-  };
+  }, [open]);
 
-  handleBackdropRef = ref => {
+  const handleBackdropRef = React.useCallback(instance => {
     // #StrictMode ready
-    this.backdropRef = ReactDOM.findDOMNode(ref);
-  };
+    backdropRef.current = ReactDOM.findDOMNode(instance);
+  }, []);
 
-  handlePaperRef = ref => {
+  const handlePaperRef = React.useCallback(instance => {
     // #StrictMode ready
-    this.paperRef = ReactDOM.findDOMNode(ref);
-  };
-
-  listenTouchStart() {
-    document.body.addEventListener('touchstart', this.handleBodyTouchStart);
-  }
-
-  removeTouchStart() {
-    document.body.removeEventListener('touchstart', this.handleBodyTouchStart);
-  }
-
-  removeBodyTouchListeners() {
-    document.body.removeEventListener('touchmove', this.handleBodyTouchMove, { passive: false });
-    document.body.removeEventListener('touchend', this.handleBodyTouchEnd);
-    document.body.removeEventListener('touchcancel', this.handleBodyTouchEnd);
-  }
-
-  render() {
-    const {
-      anchor,
-      disableBackdropTransition,
-      disableDiscovery,
-      disableSwipeToOpen,
-      hysteresis,
-      innerRef,
-      minFlingVelocity,
-      ModalProps: { BackdropProps, ...ModalPropsProp } = {},
-      onOpen,
-      open,
-      PaperProps = {},
-      SwipeAreaProps,
-      swipeAreaWidth,
-      variant,
-      ...other
-    } = this.props;
-    const { maybeSwiping } = this.state;
-
-    return (
-      <React.Fragment>
-        <Drawer
-          open={variant === 'temporary' && maybeSwiping ? true : open}
-          variant={variant}
-          ModalProps={{
-            BackdropProps: {
-              ...BackdropProps,
-              ref: this.handleBackdropRef,
-            },
-            ...ModalPropsProp,
-          }}
-          PaperProps={{
-            ...PaperProps,
-            style: {
-              pointerEvents: variant === 'temporary' && !open ? 'none' : '',
-              ...PaperProps.style,
-            },
-            ref: this.handlePaperRef,
-          }}
-          anchor={anchor}
-          ref={innerRef}
-          {...other}
-        />
-        {!disableSwipeToOpen && variant === 'temporary' && (
-          <NoSsr>
-            <SwipeArea
-              anchor={anchor}
-              innerRef={this.swipeAreaRef}
-              width={swipeAreaWidth}
-              {...SwipeAreaProps}
-            />
-          </NoSsr>
-        )}
-      </React.Fragment>
-    );
-  }
-}
+    paperRef.current = ReactDOM.findDOMNode(instance);
+  }, []);
+
+  return (
+    <React.Fragment>
+      <Drawer
+        open={variant === 'temporary' && maybeSwiping ? true : open}
+        variant={variant}
+        ModalProps={{
+          BackdropProps: {
+            ...BackdropProps,
+            ref: handleBackdropRef,
+          },
+          ...ModalPropsProp,
+        }}
+        PaperProps={{
+          ...PaperProps,
+          style: {
+            pointerEvents: variant === 'temporary' && !open ? 'none' : '',
+            ...PaperProps.style,
+          },
+          ref: handlePaperRef,
+        }}
+        anchor={anchor}
+        transitionDuration={transitionDuration}
+        onClose={onClose}
+        ref={ref}
+        {...other}
+      />
+      {!disableSwipeToOpen && variant === 'temporary' && (
+        <NoSsr>
+          <SwipeArea
+            anchor={anchor}
+            ref={swipeAreaRef}
+            width={swipeAreaWidth}
+            {...SwipeAreaProps}
+          />
+        </NoSsr>
+      )}
+    </React.Fragment>
+  );
+});
 
 SwipeableDrawer.propTypes = {
   /**
@@ -438,11 +448,6 @@ SwipeableDrawer.propTypes = {
    * Specified as percent (0-1) of the width of the drawer
    */
   hysteresis: PropTypes.number,
-  /**
-   * @ignore
-   * from `withForwardedRef`
-   */
-  innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
   /**
    * Defines, from which (average) velocity on, the swipe is
    * defined as complete although hysteresis isn't reached.
@@ -519,4 +524,4 @@ SwipeableDrawer.defaultProps = {
   variant: 'temporary', // Mobile first.
 };
 
-export default withTheme(withForwardedRef(SwipeableDrawer));
+export default withTheme(SwipeableDrawer);


Test patch:
diff --git a/packages/material-ui/src/SwipeableDrawer/SwipeableDrawer.test.js b/packages/material-ui/src/SwipeableDrawer/SwipeableDrawer.test.js
--- a/packages/material-ui/src/SwipeableDrawer/SwipeableDrawer.test.js
+++ b/packages/material-ui/src/SwipeableDrawer/SwipeableDrawer.test.js
@@ -137,7 +137,7 @@ describe('<SwipeableDrawer />', () => {
           open={false}
           PaperProps={{ component: FakePaper }}
         >
-          <h1>Hello</h1>
+          <h1>SwipeableDrawer</h1>
         </SwipeableDrawer>,
       );
     });
@@ -229,7 +229,7 @@ describe('<SwipeableDrawer />', () => {
           fireBodyMouseEvent('touchmove', { touches: [params.openTouches[1]] });
           fireBodyMouseEvent('touchmove', { touches: [params.openTouches[2]] });
           fireBodyMouseEvent('touchend', { changedTouches: [params.openTouches[2]] });
-          assert.strictEqual(handleOpen.callCount, 1);
+          assert.strictEqual(handleOpen.callCount, 1, 'open');
 
           const handleClose = spy();
           wrapper.setProps({ open: true, onClose: handleClose });
@@ -237,7 +237,7 @@ describe('<SwipeableDrawer />', () => {
           fireBodyMouseEvent('touchmove', { touches: [params.closeTouches[1]] });
           fireBodyMouseEvent('touchmove', { touches: [params.closeTouches[2]] });
           fireBodyMouseEvent('touchend', { changedTouches: [params.closeTouches[2]] });
-          assert.strictEqual(handleClose.callCount, 1);
+          assert.strictEqual(handleClose.callCount, 1, 'close');
         });
 
         it('should stay closed when not swiping far enough', () => {
@@ -376,7 +376,7 @@ describe('<SwipeableDrawer />', () => {
           open={false}
           PaperProps={{ component: FakePaper }}
         >
-          <h1>Hello</h1>
+          <h1>SwipeableDrawer</h1>
         </SwipeableDrawer>,
       );
 
@@ -400,7 +400,7 @@ describe('<SwipeableDrawer />', () => {
           open
           PaperProps={{ component: FakePaper }}
         >
-          <h1>Hello</h1>
+          <h1>SwipeableDrawer</h1>
         </SwipeableDrawer>,
       );
 
@@ -426,7 +426,7 @@ describe('<SwipeableDrawer />', () => {
             open={false}
             PaperProps={{ component: FakePaper }}
           >
-            <h1>Hello1</h1>
+            <h1>Drawer1</h1>
           </SwipeableDrawer>
           <SwipeableDrawer
             onOpen={handleOpen}
@@ -434,7 +434,7 @@ describe('<SwipeableDrawer />', () => {
             open={false}
             PaperProps={{ component: FakePaper }}
           >
-            <h1>Hello2</h1>
+            <h1>Drawer2</h1>
           </SwipeableDrawer>
         </div>,
       );
@@ -460,7 +460,7 @@ describe('<SwipeableDrawer />', () => {
         open={false}
         PaperProps={{ component: NullPaper }}
       >
-        <h1>Hello</h1>
+        <h1>SwipeableDrawer</h1>
       </SwipeableDrawer>,
     );
     fireSwipeAreaMouseEvent(wrapper, 'touchstart', { touches: [{ pageX: 0, clientY: 0 }] });
@@ -507,7 +507,7 @@ describe('<SwipeableDrawer />', () => {
       assert.strictEqual(consoleErrorMock.callCount(), 1);
       assert.include(
         consoleErrorMock.args()[0][0],
-        'Warning: Failed prop type: Invalid prop `PaperProps.component` supplied to `SwipeableDrawer`. Expected an element type that can hold a ref.',
+        'Warning: Failed prop type: Invalid prop `PaperProps.component` supplied to `ForwardRef(SwipeableDrawer)`. Expected an element type that can hold a ref.',
       );
     });
 
@@ -524,7 +524,7 @@ describe('<SwipeableDrawer />', () => {
       assert.strictEqual(consoleErrorMock.callCount(), 1);
       assert.include(
         consoleErrorMock.args()[0][0],
-        'Warning: Failed prop type: Invalid prop `ModalProps.BackdropProps.component` supplied to `SwipeableDrawer`. Expected an element type that can hold a ref.',
+        'Warning: Failed prop type: Invalid prop `ModalProps.BackdropProps.component` supplied to `ForwardRef(SwipeableDrawer)`. Expected an element type that can hold a ref.',
       );
     });
   });