Creating an iOS-like Slide-Up Drawer with React Portals
Tags: webdev, javascript, beginners, react
Published: 2024-07-05 12:33:35

## So...
Have you ever been browsing a website, perhaps on your mobile device, and marvelled at the smooth, elegant slide-up drawers that seamlessly appear at the bottom of the screen? Or maybe you've interacted with a modal that felt so intuitive and flexible that you wondered, "How do they do that?" Well, that's precisely the question I had in mind. I wanted to create a slide-up drawer that not only looks great but is also highly reusable and can accept any kind of children. This journey led me to discover the power of React Portals.


## The Drawer


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xprl8mubasbfezxhi786.gif)


## and...
The task was clear, I needed to create a slide-up drawer in React that would always be a sibling of the `<body>` element, ensuring it would overlay everything else on the page. It also needed to be flexible enough to accept any UI components as its content. Achieving this while maintaining a smooth user experience and clean codebase seemed like a daunting task.

## hello portals...
The solution came in the form of React Portals. [React Portals](https://legacy.reactjs.org/docs/portals.html) provide a way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. This feature is perfect for creating modals, tooltips, and, in my case, a slide-up drawer.

Here’s how I did it.

> GitHub now has everything and so does this code... [here is the repository](https://github.com/bansalvks/hello-bottom-sheet)
> and the [Live Preview](https://bansalvks.github.io/hello-bottom-sheet/) too

> _Note: Open the dev console and change the viewport to mobile view to see the drawer in action 😄_


## goals...

**Separation of Concerns:** By using React Portals, I could keep the drawer's DOM structure separate from the parent component's DOM, leading to better-organized code.

**Reusability:** The drawer component could accept any children, making it highly reusable across different parts of the application.

**Smooth Animations:** With CSS transitions, I could achieve smooth and visually appealing slide-up and slide-down animations.


## code...

**Step 1: Creating the SlideUpDrawer Component**

React Portals allow you to render a component outside its parent component's DOM hierarchy. This means you can create a component that appears as a sibling to the `<body>` element instead of being nested inside another component. This is especially useful for components like modals, tooltips, and our slide-up drawer because it ensures that they overlay everything else on the page.

Here's how you can create a slide-up drawer using React Portals:

```javascript
import React, { useState, ReactNode } from 'react';
import ReactDOM from 'react-dom';
import './SlideUpDrawer.css';

interface SlideUpDrawerProps {
    children: ReactNode;
}

const SlideUpDrawer: React.FC<SlideUpDrawerProps> = ({ children }) => {
    const [isOpen, setIsOpen] = useState(false);

    const toggleDrawer = () => {
        setIsOpen(!isOpen);
    };

    return ReactDOM.createPortal(
        <div className="drawer-container">
            <button className="open-drawer-button" onClick={toggleDrawer}>
                {isOpen ? 'Close' : 'Open'} Drawer
            </button>
            <div className={`drawer ${isOpen ? 'open' : ''}`}>
                <div className="drawer-content">
                    {children}
                    <button onClick={toggleDrawer}>Close</button>
                </div>
            </div>
        </div>,
        document.body
    );
};

export default SlideUpDrawer;
```

**Step 2: Styling**
Next, I wrote some CSS, and the goal was to achieve a smooth slide-up and slide-down effect.

I am using CSS transitions, which allow you to change property values smoothly over a given duration. This makes animations like sliding a drawer up and down feel natural and responsive. The transition property in CSS is key to achieving this smoothness.

- The `.drawer` class is initially positioned off-screen (`bottom: -100%`).
- When the `.drawer.open` class is added, it slides up to `bottom: 0`.
- The `transition: bottom 0.3s ease` ensures the slide-up and slide-down animations are smooth and take 0.3 seconds.

Read this awesome blog on [great animations by Emil Kowalski](https://emilkowal.ski/ui/great-animations)

```css
.drawer-container {
    position: fixed;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 50%;
    overflow: hidden;
    z-index: 1000;
}

.open-drawer-button {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1001;
}

.drawer {
    position: fixed;
    bottom: -100%;
    left: 0;
    width: 100%;
    height: 50%;
    background-color: white;
    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
    transition: bottom 0.3s ease;
    z-index: 1000;
}

.drawer.open {
    bottom: 0;
}

.drawer-content {
    padding: 20px;
}
```

**Step 2: Usage**
To see the drawer in action, use the SlideUpDrawer in the main application component and pass some children to it.

```javascript
import React from 'react';
import SlideUpDrawer from './SlideUpDrawer';

const App: React.FC = () => {
    return (
        <div>
            <h1>Slide Up Drawer Example</h1>
            <SlideUpDrawer>
                <p>This is the content of the drawer.</p>
                <p>You can add any UI components here!</p>
            </SlideUpDrawer>
        </div>
    );
};

export default App;
```

## understanding...
By leveraging React Portals, I was able to create a versatile and reusable slide-up drawer component in React. This component is reusable, capable of accepting any children and smooth 🤩

React Portals provided the key solution to rendering the drawer as a sibling of the `<body>` element, maintaining a clean and manageable codebase. The combination of TypeScript and CSS transitions ensured a robust and smooth user experience.

Feel free to customize and extend the component to fit your specific needs. Contributions and improvements are always welcome!