How to Create A Bottom Drawer In ReactJS Using Ionic Framework Components and Gesture API
Tags: react, javascript, ionic, tutorial
Published: 2020-05-11 13:30:14

Ionic Framework Gesture API makes it easy to create animations and effects in your mobile application. This is a walkthrough of a simple project where we are using the Ionic Framework Gesture API to implement a custom bottom-drawer component.

###The Video
{% youtube mOGDGxbUKu8 %}
###Lets Get Started
So the goal here is to have the drawer with only 10px displayed when 
closed, which leaves room for button or handle to start drag

```css
.bottom-drawer {
  position: absolute;
  right: 4px;
  left: 4px;
  bottom: -380px;
  height: 400px;
  border-radius: 30px;
}
```
Set the class name for styling the drawer, `bottom-drawer` and then get reference to the element so we can attach the gestureAPI to the object. We are using the react-hooks `useRef` call to get the element.

The `IonButton` is styled a bit, but we are just using it as something the click to start the drag to open it is also used to toggle the state of the bottom drawer.

When clicked, the `onClick` handler calls a function `toggleDrawer` to open or close the menu based on it's current state. 
```react
<IonApp>
  <IonHeader>
    <IonToolbar />
  </IonHeader>
  <IonContent scrollY={false} className="ion-padding">

    <IonCard className="bottom-drawer" ref={drawerRef}>
      <div style={{ textAlign: "center" }}>
        <IonButton
          size="small"
          style={{ height: 10 }}
          onClick={toggleDrawer}
        />
      </div>
      <IonCardHeader>Bottom Drawer</IonCardHeader>
    </IonCard>
  </IonContent>
</IonApp>
```
###Getting The Element
Using react-hooks [`useRef`](https://reactjs.org/docs/hooks-reference.html#useref) to get the element, the value we actually need is the `drawerRef.current`.

>As a note, the same can be accomplished by using the DOM query function `document.getElementsByClassName`
```react
document.getElementsByClassName("bottom-drawer")
```
```react
const drawerRef = useRef();

... below in the render

<IonCard className="bottom-drawer" ref={drawerRef}>
</IonCard>
```
###Attaching The Gesture
We get the reference and use that value as the element to attach the gesture to; name it and then indicate we are focusing on the `y-axis` as the direction for this gesture.
> Documentation for the [Ionic Gesture API](https://ionicframework.com/docs/utilities/gestures)
```react
 useEffect(() => {
    let c = drawerRef.current;
    const gesture = createGesture({
      el: c,
      gestureName: "my-swipe",
      direction: "y",
      onMove : (event)=> {},
      onEnd : (event)=> {}
  }, []);
```
We are focusing on two of the handlers that are available with the Gesture API, `onMove` and `onEnd`.

With the `onMove` handler we detect that the DOM element is has received and event and is starting to move, we get the change in value, `event.deltaY`, from the event and reposition the element using [`translateY`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translateY)

We check if the user is dragging beyond the desired delta `-300`, and if so we stop repositioning the element because we don't want to open the bottom drawer beyond it's height.

To provide a better user experience, if the user has started to drag the element more than a delta of `20`, we assume they want to close the bottom-drawer element so we will use some animation and reposition the element to it full closed position.
```react
onMove: event => {
  if (event.deltaY < -300) return;

 // closing with a downward swipe
 if (event.deltaY > 20) {
   c.style.transform = "";
   c.dataset.open = "false";
   return;
 }
  c.style.transform = `translateY(${event.deltaY}px)`;
},
```
To provide a better user experience, if the user has started to drag the element more than a delta of `-30`, we assume they want to open the bottom-drawer element so we will use some animate and reposition the element to it full open position.
```react
onEnd: event => {
  c.style.transition = ".5s ease-out";

  if (event.deltaY < -30 && c.dataset.open != "true") {
    c.style.transform = `translateY(${-350}px) `;
    c.dataset.open = "true";
  }
}
```
You noticed in the code above we have been using the `dataset.open` attribute on the element that we are manipulating. This custom attribute holds the state of the bottom-drawer. 
>[Using Data Attributes in HTML](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes)

Yes you could have managed the state in the react application but I chose to do it this way.


###Handling the Button Click
Since we now have the proper animations and delta thresholds figured out, we can use them as a response to a click event on a button to determine how to open or close the drawer.

And as mentioned in the last section we have access to the `dataset.open` attribute to let us know how to toggle the drawer open and closed based on the mouse click.
```javascript
const toggleDrawer = () => {
  let c = drawerRef.current;
  if (c.dataset.open === "true") {
    c.style.transition = ".5s ease-out";
    c.style.transform = "";
    c.dataset.open = "false";
  } else {
    c.style.transition = ".5s ease-in";
    c.style.transform = `translateY(${-350}px) `;
    c.dataset.open = "true";
  }
};
```

###Conclusion
This is a simple example of the power of the new [Gesture API in Ionic Framework](https://ionicframework.com/docs/utilities/gestures). This BottomDrawer implementation works, but I am certain there are some tweaks to make it more robust and I am open to hearing some feedback.

Please take a look at the rest of the content I have posted on reactjs and [Ionic Framework](https://ionicframework.com/docs/) here on [my Dev.To profile](https://dev.to/aaronksaunders) and also there are videos posted on [my YouTube Channel]()

###On CodeSandbox
- https://codesandbox.io/s/ionic-react-gesture-7s6yd?file=/src/App.js

###Full Source Code 
```react
// App.js
import React, { useEffect, useRef } from "react";
import {
  IonApp,
  IonContent,
  IonButton,
  IonCard,
  IonHeader,
  IonToolbar,
  createGesture,
  IonCardHeader
} from "@ionic/react";

/* Core CSS required for Ionic components to work properly */
import "@ionic/react/css/core.css";

/* Basic CSS for apps built with Ionic */
import "@ionic/react/css/normalize.css";
import "@ionic/react/css/structure.css";
import "@ionic/react/css/typography.css";

/* Optional CSS utils that can be commented out */
import "@ionic/react/css/padding.css";
import "@ionic/react/css/float-elements.css";
import "@ionic/react/css/text-alignment.css";
import "@ionic/react/css/text-transformation.css";
import "@ionic/react/css/flex-utils.css";
import "@ionic/react/css/display.css";

import "/App.css";

const App = () => {
  const drawerRef = useRef();

  // when the page is loaded, we find the element that is the drawer
  // and attach the gesture to it's reference using react `useRef` hook
  useEffect(() => {
    let c = drawerRef.current;
    const gesture = createGesture({
      el: c,
      gestureName: "my-swipe",
      direction: "y",
      /**
       * when moving, we start to show more of the drawer
       */
      onMove: event => {
        if (event.deltaY < -300) return;

        // closing with a downward swipe
        if (event.deltaY > 20) {
          c.style.transform = "";
          c.dataset.open = "false";
          return;
        }

        c.style.transform = `translateY(${event.deltaY}px)`;
      },
      /**
       * when the moving is done, based on a specific delta in the movement; in this
       * case that value is -150, we determining the user wants to open the drawer.
       *
       * if not we just reset the drawer state to closed
       */
      onEnd: event => {
        c.style.transition = ".5s ease-out";

        if (event.deltaY < -30 && c.dataset.open !== "true") {
          c.style.transform = `translateY(${-350}px) `;
          c.dataset.open = "true";
          console.log("in on end");
        }
      }
    });

    // enable the gesture for the item
    gesture.enable(true);
  }, []);

  /**
   * this function is called when the button on the top of the drawer
   * is clicked.  We are using the data-set attributes on the element
   * to determine the state of the drawer.
   *
   * this could be done using react state if you like.
   */
  const toggleDrawer = () => {
    let c = drawerRef.current;
    if (c.dataset.open === "true") {
      c.style.transition = ".5s ease-out";
      c.style.transform = "";
      c.dataset.open = "false";
    } else {
      c.style.transition = ".5s ease-in";
      c.style.transform = `translateY(${-350}px) `;
      c.dataset.open = "true";
    }
  };

  return (
    <IonApp>
      <IonHeader>
        <IonToolbar />
      </IonHeader>
      <IonContent scrollY={false} className="ion-padding">
        <p>
          Sample project using Gesture API from Ionic Framework to create a
          bottom drawer
        </p>
        <ul>
          <li> Click button to open or close the drawer</li>
          <li> Drag to open or close</li>
        </ul>
        {/* 
    Set the class name for styling the drawer and then get reference
    so we can attach the gestureAPI to the object 
    */}
        <IonCard className="bottom-drawer" ref={drawerRef}>
          <div style={{ textAlign: "center" }}>
            <IonButton
              size="small"
              style={{ height: 10 }}
              onClick={toggleDrawer}
            />
          </div>
          <IonCardHeader>Bottom Drawer</IonCardHeader>
        </IonCard>
      </IonContent>
    </IonApp>
  );
};

export default App;
```
```css
/* App.css
so the goal here is to have the drawer with only 10px displayed when 
closed, which leaves room for button or handle to start drag
*/
.bottom-drawer {
  position: absolute;
  right: 4px;
  left: 4px;
  bottom: -380px;
  height: 400px;
  border-radius: 30px;
}

```