Bootstrap Modal Dialog in React without JQuery

The Task

I want the modal dialog component working in a React project. My React project uses function components and hooks but it would be the same with class components.

What I want – a lovely modal dialog component

The Problem

To show the modal I need to call it thus

$('#myModal').modal('show')

It’s not going to work as is in React. There is no JQuery and I don’t want to be referencing DOM elements directly if I can avoid it.

The Solution

I’m going to roll my own and use useState react hook to swap the classes in and out to show and hide it.

Justification

I could import JQuery and use a useRef hook to the DOM. But I don’t want to install JQuery into my project just so I can have a modal dialog behaving appropriately. It should be possible without it.

Alternatively I could use React-Bootstrap components. But it feels a bit abstracted to me. In any case I just want the modal working not a heap of other bootstrap components. Also, I want to use the latest bootstrap and not be forced to use the earlier one in React-Bootstrap.

So I’m going to roll my own. No true programmer would save time by using third party components or by taking shortcuts. They do it themselves and take much longer about it. I shall do it this way **

** I use third party components and shortcuts all the time. I’m just not going to for this one.

Implementation

Full implementation is on my git hub site

https://github.com/timbrownls20/Demo/tree/master/React/bootstrap-modal

so I recommend that you go straight there and copy the code down. It’s stripped down to basics and it’s pretty straight forward

Modal Component

https://github.com/timbrownls20/Demo/blob/master/React/bootstrap-modal/src/components/Modal.jsx

import React from 'react';

const Modal = ({ children, show, hideModal }) => (
  <div
    className={`modal ${show ? ' modal-show' : ''}`}
    tabIndex="-1"
    role="dialog"
  >
    <div className="modal-dialog" role="document">
      <div className="modal-content">
        <div className="modal-header">
          <h5 className="modal-title">Modal Title</h5>
          <button
            type="button"
            className="close"
            data-dismiss="modal"
            aria-label="Close"
            onClick={hideModal}
          >
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div className="modal-body">
          {children}
        </div>
        <div className="modal-footer">
          <button type="button" className="btn btn-primary" onClick={hideModal}>
            Save
          </button>
          <button
            type="button"
            className="btn btn-secondary"
            data-dismiss="modal"
            onClick={hideModal}
          >
            Close
          </button>
        </div>
      </div>
    </div>
  </div>
);

export default Modal;

It’s a straight copy from the bootstrap documentation with a prop to toggle the visibility and a function to response to close and save.

State

https://github.com/timbrownls20/Demo/blob/master/React/bootstrap-modal/src/components/App.jsx

import React, { useState } from "react";
import Modal from "./Modal";
import ModalLauncher from "./ModalLauncher";
import '../css/modal.css';

function App() {
  const [show, setShow] = useState(false);

  const showModal = () => {
    setShow(true);
  };

  const hideModal = () => {
    setShow(false);
  };

  return (
    <div>
      <div className="container-fluid">
      <div className="d-flex justify-content-center align-content-center m-5">
            <div className="p-5 demo-text">front page text</div>
            <ModalLauncher showModal={showModal} />
            <div className="p-5 demo-text">More front page text</div>
        </div>
        <Modal show={show} hideModal={hideModal}>Modal content</Modal>
      </div>
    </div>
  );
}

export default App;

The show and hide state is set at the parent component level and is persisted through a state hook taking a boolean i.e.

const [show, setShow] = useState(false);
CSS

The detail of it is really in the CSS and it’s that I had t spend time fiddling around with

https://github.com/timbrownls20/Demo/blob/master/React/bootstrap-modal/src/css/modal.css

.modal{
    display: block;
    visibility: hidden;
}

.modal-show
{
    visibility:visible;
    background-color: rgba(169, 169, 169, 0.8);
    transition: opacity 0.2s linear; 
} 

.modal-content 
{
    opacity: 0; 
}

.modal-show .modal-content 
{
    opacity: 1; 
    transition: 0.2s linear; 
} 

To get the modal to appear and disappear is straight forward but I want it to show with a transition to fade it in and out, otherwise it looks terrible. That’s a bit more awkward.

To do this, the modal styles are overridden. The visibility in bootstrap is controlled by the CSS display property. I’ve changed it to use the visibility property. If the showing and hiding is managed by changing display from none to block then the transitions don’t work. Visibility does work with transitions, hence the change

.modal-show is the class that we add and remove to show and hide the modal dialog. We use opacity to bring in the dialog. Interestingly if I use

.modal-show
{
    visibility:visible;
    background-color: rgba(169, 169, 169);
    opacity: 0.5;
    transition: opacity 0.2s linear; 
} 

Then the dialog and the background have an opacity of 0.8 and I’ve got a see through modal which I definitely don’t want.

See through modal dialog. Nasty

Setting the opacity with background-colour just applies it to the background overlay and the dialog itself isn’t affected and is not see through at all.

The final wrinkle is that now the dialog doesn’t transition in, although the gray background overlay does. It actually looks OK to me (but then I’m not a designer so I don’t care hugely about this stuff). To get the dialog fading in and out it needs its own CSS transition, this time on opacity i.e.

.modal-content 
{
    opacity: 0; 
}

.modal-show .modal-content 
{
    opacity: 1; 
    transition: 0.2s linear; 
}

Now the background overlay fades in to 0.8 opacity and the modal dialog fades in to opacity 1 i.e. fully visible.

I could spend more time playing around with the CSS to improve it further and believe me I’m tempted to do just that. However, I’ve got a paid job to do and a daughter who is insisting that I play Minecraft with her (which is what passes for parenting these days). So, I am declaring it job done!

Useful Links

This Stack overflow question gives a whole bunch of implementations for this but they do use JQuery which I wanted to avoid. It might well be the definition of madness to implement something that has already got a bunch of alternative implementations. I just wanted to do it without JQuery.

Bootstrap modal dialog documentation. The JQuery does more than open and close the dialog but that’s all I need it to do right now

React-Bootstrap components. I’ve tried these but I just don’t like them. I’ve watched pluralsight videos where people are very keen on them so it’s probably just me.

Leave a Reply

Your email address will not be published. Required fields are marked *