How to create a reusable React modal component

React

How to create a reusable React modal component

Modals are almost an essential part of every website. You have them in various shapes and sizes asking you to confirm some changes, alerting you on some dangerous actions, or just politely offering that you sign up on some website newsletter. 

If not looking for someone’s modal npm package React doesn’t offer much out of the box for creating modals. So, we can just use our imagination and glue some pieces of work into a cool little modal component that you can reuse on any future project.

I will start by stealing a concept that we have in Vue.js. It is about slot api that Vue provides.

The <slot> elements serve as distribution outlets for content which allows for a lot of power when building reusable components.

Let’s say that you have some global button component

<button class="btn-primary">
  <slot></slot>
</button>


Then in some other component or view when using this button component we can just pass anything inside this button and it will be rendered instead of the slot element.

<button class="btn-primary">
  Preview
</button>


I want to mimic this same behavior with react. We will use Fragments and a bit of custom code to achieve almost the same behavior.

 

First, we will create a Modal.js component and add a basic layout code.

import React from 'react'
import './Modal.scss'

const Modal = (props) => {

   return (
       <div className='modal-mask modal-close'>
           <div className='modal-wrapper'>
               <div className='modal-container'>

                   <div className='modal-header'>
                   </div>

                   <div className='modal-body'>
                   </div>

                   <div className='modal-footer'>
                       <button className='modal-close'>Close</button>
                    
                   </div>

               </div>
           </div>
       </div>
   );
}

export default Modal


And let’s also create a Modal.scss file.

@import '../../assets/scss/variables';

.modal-mask {
   position: fixed;
   z-index: 9998;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
   background-color: rgba(0, 0, 0, 0.5);
   display: table;
   transition: opacity 0.3s ease;

   .modal-wrapper {
       margin: 2rem 10px 10px 10px;

       .modal-container {
           max-width: 400px;
           margin: 0px auto;
           border-radius: 2px;
           box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
           transition: all 0.3s ease;
           font-family: Helvetica, Arial, sans-serif;

           .modal-header {
               background-color: $bg-main;
               padding: 10px;
               color: white;
               border-top-left-radius: 10px;
               border-top-right-radius: 10px;
               text-align: center;
           }

           .modal-body {
               padding: 20px;
               background-color: white;
               text-align: left;

               input {
                   background-color: #f5f6f8;
                   border: none;
                   padding: 10px;
                   width: 100%;
                   box-sizing: border-box;
               }
           }

           .modal-footer {
               padding: 10px;
               background-color: white;
               display: flex;
               justify-content: space-between;
               border-bottom-left-radius: 10px;
               border-bottom-right-radius: 10px;

               button {
                   background-color: $bg-main;
                   border: none;
                   padding: 5px;
                   width: 70px;
                   color: white;
                   border-radius: 10px;
                   cursor: pointer;
                   font-weight: 600;
               }
           }
       }
   }
}


I want to use my modal as the following.

<Modal click={() => setShowModal(false)}>
   
   <Fragment key='header'>
       <h3 className='m-0'>Delete product</h3>
   </Fragment>

   <Fragment key='body'>
       <p>Are you sure you want to delete this product?</p>
   </Fragment>

   <Fragment key='footer'>
       <Button onClick={handleDeleteProduct}>Yes</Button> 
    </Fragment>

</Modal>


We need to find a way to receive elements passed within <Fragment> and render them in the specified place. 

For that, we can use the key attribute where we set the value as the place where we want to render our content: header, body, or footer.

Inside our Modal component, we then need a function that can look through all of the children elements passed and match them by the key attribute.

const findByKey = (name) =>
   props.children.map(child => {
       if (child.key === name) return child
   })


And then our updated Modal looks like this.

<div className='modal-header'>
   {findByKey('header')}
</div>

<div className='modal-body'>
   {findByKey('body')}
</div>

<div className='modal-footer'>
   <button className='modal-close' onClick={closeModal}>Close</button>
   {findByKey('footer')}
</div>


We are calling our function from each of the three sections that we have and passing which key should be matched so that we render an appropriate element in that place.

Pretty neat.

The only thing left to do is to hook a function that will close down the modal if we click outside of it or if we click on the close button.

const closeModal = (e) => {
   e.stopPropagation()

   if (e.target.classList.contains('modal-close')) {
       return props.click()
   }
}


Our full modal component looks like this:

import React from 'react'
import './Modal.scss'

const Modal = (props) => {

   const findByKey = (name) =>
       props.children.map(child => {
           if (child.key === name) return child
       })

       const closeModal = (e) => {
           e.stopPropagation()

           if (e.target.classList.contains('modal-close')) {
               return props.click()
           }
       }

   return (
       <div className='modal-mask modal-close' onClick={closeModal}>
           <div className='modal-wrapper'>
               <div className='modal-container'>

                   <div className='modal-header'>
                       {findByKey('header')}
                   </div>

                   <div className='modal-body'>
                       {findByKey('body')}
                   </div>

                   <div className='modal-footer'>
                       <button className='modal-close' onClick={closeModal}>Close</button>
                       {findByKey('footer')}
                   </div>

               </div>
           </div>
       </div>
   );
}

export default Modal

 

Show comments

Laravel

5 min

How to make Laravel authentication

Laravel provides a neat function that quickly generates a scaffold of routes, views, and controllers used for authentication.

Laravel

7 min

How to install Laravel application

This article will cover how to install and create a local development environment for your Laravel application. There are only a couple of steps that needs to be done.

Laravel

3 min

How to set appropriate Laravel permissions on Linux server

After publishing your Laravel application to a real web server, you discover then some of your functionalities are failing.

Codinary