70

I need to open a Bootstrap Modal from clicking on a button in a Bootstrap navbar and other places (to show data for a component instance, ie. providing "editing" functionality), but I don't know how to accomplish this. Here is my code:

EDIT: Code updated.

ApplicationContainer = React.createClass({
    render: function() {
        return (
            <div className="container-fluid">
            <NavBar />
                <div className="row">
                    <div className="col-md-2">
                        <ScheduleEntryList />
                    </div>
                    <div className="col-md-10">
                    </div>
                </div>
                <ScheduleEntryModal />
            </div>
        );
    }
});

NavBar = React.createClass({
    render: function() {
        return (
            <nav className="navbar navbar-default navbar-fixed-top">
                <div className="container-fluid">
                    <div className="navbar-header">
                        <a className="navbar-brand" href="#">
                            <span className="glyphicon glyphicon-eye-open"></span>
                        </a>
                    </div>
                    <form className="navbar-form navbar-left">
                        <button className="btn btn-primary" type="button" data-toggle="modal" data-target="#scheduleentry-modal">
                            <span className="glyphicon glyphicon-plus">
                            </span>
                        </button>
                    </form>
                    <ul className="nav navbar-nav navbar-right">
                        <li><a href="#"><span className="glyphicon glyphicon-user"></span> Username</a></li>
                    </ul>
                </div>
            </nav>
        );
    }
});

ScheduleEntryList = React.createClass({
    getInitialState: function() {
        return {data: []}
    },

    loadData: function() {
        $.ajax({
            url: "/api/tasks",
            dataType: "json",

            success: function(data) {
                this.setState({data: data});
            }.bind(this),

            error: function(xhr, status, error) {
                console.error("/api/tasks", status, error.toString());
            }.bind(this)
        });
    },

    componentWillMount: function() {
        this.loadData();
        setInterval(this.loadData, 20000);
    },

    render: function() {
        items = this.state.data.map(function(item) {
            return <ScheduleEntryListItem item={item}></ScheduleEntryListItem>;
        });

        return (
            <div className="list-group">
                <a className="list-group-item active">
                    <h5 className="list-group-item-heading">Upcoming</h5>
                </a>
                {items}
            </div>
        );
    }
});

ScheduleEntryListItem = React.createClass({
    openModal: function() {
        $("#scheduleentry-modal").modal("show");
    },

    render: function() {
        deadline = moment(this.props.item.deadline).format("MMM Do YYYY, h:mm A");

        return (
            <a className="list-group-item" href="#" onClick={this.openModal}>
                <h5 className="list-group-item-heading">
                    {this.props.item.title}
                </h5>
                <small className="list-group-item-text">
                    {deadline}
                </small>
            </a>
        );
    }
});

Modal = React.createClass({
    componentDidMount: function() {
        $(this.getDOMNode())
            .modal({backdrop: "static", keyboard: true, show: false});
    },

    componentWillUnmount: function() {
        $(this.getDOMNode())
            .off("hidden", this.handleHidden);
    },

    open: function() {
        $(this.getDOMNode()).modal("show");
    },

    close: function() {
        $(this.getDOMNode()).modal("hide");
    },

    render: function() {
        return (
            <div id="scheduleentry-modal" className="modal fade" tabIndex="-1">
                <div className="modal-dialog">
                    <div className="modal-content">
                        <div className="modal-header">
                            <button type="button" className="close" data-dismiss="modal">
                                <span>&times;</span>
                            </button>
                            <h4 className="modal-title">{this.props.title}</h4>
                        </div>
                        <div className="modal-body">
                            {this.props.children}
                        </div>
                        <div className="modal-footer">
                            <button type="button" className="btn btn-danger pull-left" data-dismiss="modal">Delete</button>
                            <button type="button" className="btn btn-primary">Save</button>
                        </div>
                    </div>
                </div>
            </div>

        )
    }
});

ScheduleEntryModal = React.createClass({
    render: function() {
        var modal = null;
        modal = (
            <Modal title="Add Schedule Entry">
                    <form className="form-horizontal">
                        <div className="form-group">
                            <label htmlFor="title" className="col-sm-2 control-label">Title</label>
                            <div className="col-sm-10">
                                <input id="title" className="form-control" type="text" placeholder="Title" ref="title" name="title"/>
                            </div>
                        </div>
                        <div className="form-group">
                            <label htmlFor="deadline" className="col-sm-2 control-label">Deadline</label>
                            <div className="col-sm-10">
                                <input id="deadline" className="form-control" type="datetime-local" ref="deadline" name="deadline"/>
                            </div>
                        </div>
                        <div className="form-group">
                            <label htmlFor="completed" className="col-sm-2 control-label">Completed</label>
                            <div className="col-sm-10">
                                <input id="completed" className="form-control" type="checkbox" placeholder="completed" ref="completed" name="completed"/>
                            </div>
                        </div>
                        <div className="form-group">
                            <label htmlFor="description" className="col-sm-2 control-label">Description</label>
                            <div className="col-sm-10">
                                <textarea id="description" className="form-control" placeholder="Description" ref="description" name="description"/>
                            </div>
                        </div>
                    </form>
            </Modal>
        );

        return (
            <div className="scheduleentry-modal">
                {modal}
            </div>
        );
    }
});

Other comments and improvements to the code are appreciated.

12 Answers12

103

I was recently looking for a nice solution to this without adding React-Bootstrap to my project (as Bootstrap 4 is about to be released).

This is my solution: https://jsfiddle.net/16j1se1q/1/

let Modal = React.createClass({
    componentDidMount(){
        $(this.getDOMNode()).modal('show');
        $(this.getDOMNode()).on('hidden.bs.modal', this.props.handleHideModal);
    },
    render(){
        return (
          <div className="modal fade">
            <div className="modal-dialog">
              <div className="modal-content">
                <div className="modal-header">
                  <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                  <h4 className="modal-title">Modal title</h4>
                </div>
                <div className="modal-body">
                  <p>One fine body&hellip;</p>
                </div>
                <div className="modal-footer">
                  <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
                  <button type="button" className="btn btn-primary">Save changes</button>
                </div>
              </div>
            </div>
          </div>
        )
    },
    propTypes:{
        handleHideModal: React.PropTypes.func.isRequired
    }
});



let App = React.createClass({
    getInitialState(){
        return {view: {showModal: false}}
    },
    handleHideModal(){
        this.setState({view: {showModal: false}})
    },
    handleShowModal(){
        this.setState({view: {showModal: true}})
    },
    render(){
    return(
        <div className="row">
            <button className="btn btn-default btn-block" onClick={this.handleShowModal}>Open Modal</button>
            {this.state.view.showModal ? <Modal handleHideModal={this.handleHideModal}/> : null}
        </div>
    );
  }
});

React.render(
   <App />,
    document.getElementById('container')
);

The main idea is to only render the Modal component into the React DOM when it is to be shown (in the App components render function). I keep some 'view' state that indicates whether the Modal is currently shown or not.

The 'componentDidMount' and 'componentWillUnmount' callbacks either hide or show the modal (once it is rendered into the React DOM) via Bootstrap javascript functions.

I think this solution nicely follows the React ethos but suggestions are welcome!

tgrrr
  • 1,279
  • 1
  • 9
  • 10
  • 1
    Added `on('hidden.bs.modal', this.props.handleHideModal);` to correctly handle closing of popup. – happy.cze Oct 21 '15 at 21:42
  • @happy.cze yes I found this also! Otherwise you end up with 2 modal back drops when you close then re-open the modal. Thanks! – tgrrr Jan 18 '16 at 03:55
  • 15
    Had to replace `$(this.getDOMNode())` with `$(ReactDOM.findDOMNode(this))` to make it work. Probably not the correct approach to show a modal, but worked for me. – Esteban Bouza May 08 '16 at 07:09
  • According to https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html, .getDOMNode() is deprecating and replaced by ReactDOM.findDOMNode. So I think @EstebanBouza your approach is correct. – shaochuancs Jun 29 '16 at 08:33
  • 1
    According to http://stackoverflow.com/questions/10636667/bootstrap-modal-appearing-under-background, If the modal container has a fixed or relative or absolute position, or is within an element with fixed/relative/absolute position, then the modal will appear under background. Unfortunately, in this solution, the modal container is embedded inside react component, which means the modal-appear-under-background problem will happen once the react component is rendered inside fixed/relative/absolute DOM element. I met this problem a few hours ago and came up to a new solution. Will post it later. – shaochuancs Jul 11 '16 at 07:10
  • 4
    Guess waiting is over. Btw I don't really like the fact that your solution includes jQuery. – Maxim Zubarev Jun 01 '18 at 14:52
  • Still works in 2020, afor moderm functional componets, i had to use some combination of useRef, forwardRef and ref. – Zia Ul Rehman Mughal Jan 16 '20 at 13:40
  • Doesn't work because `'$' is not defined`. – Richard Barraclough Jan 05 '22 at 13:25
65

You can use React-Bootstrap (https://react-bootstrap.github.io/components/modal). There is an example for modals at that link. Once you have loaded react-bootstrap, the modal component can be used as a react component:

var Modal = ReactBootstrap.Modal;

can then be used as a react component as <Modal/>.

For Bootstrap 4, there is react-strap (https://reactstrap.github.io). React-Bootstrap only supports Bootstrap 3.

Mark
  • 3,113
  • 1
  • 18
  • 24
  • 1
    I believe I already have a more primitive version of this in my code. What I need is to be able to show a modal when clicking on another component (a list item) and then show the data for this list item. –  Feb 01 '15 at 02:31
  • 7
    I would still highly recommend using react-bootstrap. It will prevent you from having to reinvent the wheel, and you won't have to use jquery to mutate the DOM to get the desired modal behavior (you generally want to avoid changing the DOM directly when using react). `` can be used to launch the modal, or if you want to handle the modal's state yourself, you can use the "Custom Trigger" from the modals section of the react-bootstrap examples (linked in answer). – Mark Feb 01 '15 at 07:23
  • I revisited this and ended up using react-bootstrap, it does the job nicely. –  Apr 19 '15 at 15:32
  • It doesn't provide a Provider tag for the modal. I think you'll get in trouble if you use Redux – Igor Donin Apr 04 '17 at 00:34
  • Reactstrap works nicely. Recommended if you are using bootstrap 4 – Niraj Kumar Dec 13 '18 at 05:59
  • Seems to me like react-bootstrap 1.0.1 supports bootstrap 4 nowadays – Cold_Class May 02 '20 at 21:19
13

getDOMNode() is deprecated. Instead use ref to access DOM element. Here is working Modal component (Bootstrap 4). Decide whether to show or not to show Modal component in parent component.

Example: https://jsfiddle.net/sqfhkdcy/

class Modal extends Component {
    constructor(props) {
        super(props);
    }
    componentDidMount() {
        $(this.modal).modal('show');
        $(this.modal).on('hidden.bs.modal', handleModalCloseClick);
    }
    render() {
        return (
            <div>
                <div className="modal fade" ref={modal=> this.modal = modal} id="exampleModal" tabIndex="-1" role="dialog" aria- labelledby="exampleModalLabel" aria-hidden="true">
                    <div className="modal-dialog" role="document">
                        <div className="modal-content">
                            <div className="modal-header">
                                <h5 className="modal-title" id="exampleModalLabel">Modal title
                                </h5>
                                <button type="button" className="close" data- dismiss="modal" aria-label="Close">
                                    <span aria-hidden="true">&times;</span>
                                </button>
                            </div>
                            <div className="modal-body">
                                ...
                            </div>
                            <div className="modal-footer">
                                <button type="button" className="btn btn-secondary" data- dismiss="modal">Close</button>
                                <button type="button" className="btn btn-primary">Save changes</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

Edit:

Here are the necessary imports to make it work:

import $ from 'jquery';
window.jQuery = $;
window.$ = $;
global.jQuery = $;
Rinat Rezyapov
  • 437
  • 4
  • 12
11

I've only used bootstrap cdn (css + js) to achieve "reactstrap" like solution. I've used props.children to pass dynamic data from parent to child components. You can find more about this here. In this way you have three separate components modal header, modal body and modal footer and they are totally independent from each other.


//Modal component
import React, { Component } from 'react';

export const ModalHeader = props => {
  return <div className="modal-header">{props.children}</div>;
};

export const ModalBody = props => {
  return <div className="modal-body">{props.children}</div>;
};

export const ModalFooter = props => {
  return <div className="modal-footer">{props.children}</div>;
};

class Modal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      modalShow: '',
      display: 'none'
    };
    this.openModal = this.openModal.bind(this);
    this.closeModal = this.closeModal.bind(this);
  }

  openModal() {
    this.setState({
      modalShow: 'show',
      display: 'block'
    });
  }

  closeModal() {
    this.setState({
      modalShow: '',
      display: 'none'
    });
  }

  componentDidMount() {
    this.props.isOpen ? this.openModal() : this.closeModal();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.isOpen !== this.props.isOpen) {
      this.props.isOpen ? this.openModal() : this.closeModal();
    }
  }

  render() {
    return (
      <div
        className={'modal fade ' + this.state.modalShow}
        tabIndex="-1"
        role="dialog"
        aria-hidden="true"
        style={{ display: this.state.display }}
      >
        <div className="modal-dialog" role="document">
          <div className="modal-content">{this.props.children}</div>
        </div>
      </div>
    );
  }
}

export default Modal;

//App component
import React, { Component } from 'react';
import Modal, { ModalHeader, ModalBody, ModalFooter } from './components/Modal';

import './App.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      modal: false
    };
    this.toggle = this.toggle.bind(this);
  }

  toggle() {
    this.setState({ modal: !this.state.modal });
  }

  render() {
    return (
      <div className="App">
        <h1>Bootstrap Components</h1>

        <button
          type="button"
          className="btn btn-secondary"
          onClick={this.toggle}
        >
          Modal
        </button>

        <Modal isOpen={this.state.modal}>
          <ModalHeader>
            <h3>This is modal header</h3>
            <button
              type="button"
              className="close"
              aria-label="Close"
              onClick={this.toggle}
            >
              <span aria-hidden="true">&times;</span>
            </button>
          </ModalHeader>
          <ModalBody>
            <p>This is modal body</p>
          </ModalBody>
          <ModalFooter>
            <button
              type="button"
              className="btn btn-secondary"
              onClick={this.toggle}
            >
              Close
            </button>
            <button
              type="button"
              className="btn btn-primary"
              onClick={this.toggle}
            >
              Save changes
            </button>
          </ModalFooter>
        </Modal>
      </div>
    );
  }
}

export default App;

Szabolcs Páll
  • 1,402
  • 6
  • 25
  • 31
viktorlab
  • 111
  • 2
  • 3
6

I Created this function:

onAddListItem: function () {
    var Modal = ReactBootstrap.Modal;
    React.render((
        <Modal title='Modal title' onRequestHide={this.hideListItem}>
            <ul class="list-group">
                <li class="list-group-item">Cras justo odio</li>
                <li class="list-group-item">Dapibus ac facilisis in</li>
                <li class="list-group-item">Morbi leo risus</li>
                <li class="list-group-item">Porta ac consectetur ac</li>
                <li class="list-group-item">Vestibulum at eros</li>
            </ul>
        </Modal>
    ), document.querySelector('#modal-wrapper'));
}

And then used it on my Button trigger.

To 'hide' the Modal:

hideListItem: function () {
    React.unmountComponentAtNode(document.querySelector('#modal-wrapper'));
},
sangress
  • 599
  • 5
  • 11
6

You can use the model from the react-bootstrap from link and it's basically a function based

function Example() {
  const [show, setShow] = useState(false);
  const handleClose = () => setShow(false);
  const handleShow = () => setShow(true);
  return (
    <>
      <Button variant="primary" onClick={handleShow}>
        Launch demo modal
      </Button>

      <Modal show={show} onHide={handleClose} animation={false}>
        <Modal.Header closeButton>
          <Modal.Title>Modal heading</Modal.Title>
        </Modal.Header>
        <Modal.Body>Woohoo, you're reading this text in a modal!</Modal.Body>
        <Modal.Footer>
          <Button variant="secondary" onClick={handleClose}>
            Close
          </Button>
          <Button variant="primary" onClick={handleClose}>
            Save Changes
          </Button>
        </Modal.Footer>
      </Modal>
    </>
  );
}

and You can convert it into the class component

import React, { Component } from "react";
import { Button, Modal } from "react-bootstrap";


export default class exampleextends Component {
  constructor(props) {
    super(props);
    this.state = {
      show: false,
      close: false,
    };
  } 
  render() {
    return (
      <div>
        <Button
          variant="none"
          onClick={() => this.setState({ show: true })}
        >
          Choose Profile
        </Button>
        <Modal
          show={this.state.show}
          animation={true}
          size="md" className="" shadow-lg border">
          <Modal.Header className="bg-danger text-white text-center py-1">
            <Modal.Title className="text-center">
              <h5>Delete</h5>
            </Modal.Title>
          </Modal.Header>
          <Modal.Body className="py-0 border">
            body   
          </Modal.Body>
<Modal.Footer className="py-1 d-flex justify-content-center">
              <div>
                <Button
                  variant="outline-dark" onClick={() => this.setState({ show: false })}>Cancel</Button>
              </div>
              <div>
                <Button variant="outline-danger" className="mx-2 px-3">Delete</Button>
              </div>
            </Modal.Footer>
        </Modal>
      </div>
    );
  }
}
Arshad
  • 333
  • 4
  • 10
4

You can try this modal:https://github.com/xue2han/react-dynamic-modal It is stateless and can be rendered only when needed.So it is very easy to use.Just like this:

    class MyModal extends Component{
       render(){
          const { text } = this.props;
          return (
             <Modal
                onRequestClose={this.props.onRequestClose}
                openTimeoutMS={150}
                closeTimeoutMS={150}
                style={customStyle}>
                <h1>What you input : {text}</h1>
                <button onClick={ModalManager.close}>Close Modal</button>
             </Modal>
          );
       }
    }

    class App extends Component{
        openModal(){
           const text = this.refs.input.value;
           ModalManager.open(<MyModal text={text} onRequestClose={() => true}/>);
        }
        render(){
           return (
              <div>
                <div><input type="text" placeholder="input something" ref="input" /></div>
                <div><button type="button" onClick={this.openModal.bind(this)}>Open Modal </button> </div>
              </div>
           );
        }
    }

    ReactDOM.render(<App />,document.getElementById('main'));
xuehan
  • 571
  • 4
  • 4
4

Thanks to @tgrrr for a simple solution, especially when 3rd party library is not wanted (such as React-Bootstrap). However, this solution has a problem: modal container is embedded inside react component, which leads to modal-under-background issue when outside react component (or its parent element) has position style as fixed/relative/absolute. I met this problem and came up to a new solution:

"use strict";

var React = require('react');
var ReactDOM = require('react-dom');

var SampleModal = React.createClass({
  render: function() {
    return (
      <div className="modal fade" tabindex="-1" role="dialog">
        <div className="modal-dialog">
          <div className="modal-content">
            <div className="modal-header">
              <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
              <h4 className="modal-title">Title</h4>
            </div>
            <div className="modal-body">
              <p>Modal content</p>
            </div>
            <div className="modal-footer">
              <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button>
              <button type="button" className="btn btn-primary">OK</button>
            </div>
          </div>
        </div>
      </div>
    );
  }
});

var sampleModalId = 'sample-modal-container';
var SampleApp = React.createClass({
  handleShowSampleModal: function() {
    var modal = React.cloneElement(<SampleModal></SampleModal>);
    var modalContainer = document.createElement('div');
    modalContainer.id = sampleModalId;
    document.body.appendChild(modalContainer);
    ReactDOM.render(modal, modalContainer, function() {
      var modalObj = $('#'+sampleModalId+'>.modal');
      modalObj.modal('show');
      modalObj.on('hidden.bs.modal', this.handleHideSampleModal);
    }.bind(this));
  },
  handleHideSampleModal: function() {
    $('#'+sampleModalId).remove();
  },
  render: function(){    
    return (
      <div>
        <a href="javascript:;" onClick={this.handleShowSampleModal}>show modal</a>
      </div>
    )
  }
});

module.exports = SampleApp;

The main idea is:

  1. Clone the modal element (ReactElement object).
  2. Create a div element and insert it into document body.
  3. Render the cloned modal element in the newly inserted div element.
  4. When the render is finished, show modal. Also, attach an event listener, so that when modal is hidden, the newly inserted div element will be removed.
Community
  • 1
  • 1
shaochuancs
  • 15,342
  • 3
  • 54
  • 62
  • Thank you, couldn't figure out how to get rid of the under-background issue and your suggestion worked perfectly! – Joon Nov 08 '18 at 22:00
4

Reactstrap also has an implementation of Bootstrap Modals in React. This library targets Bootstrap version 4, whereas react-bootstrap targets version 3.X.

curran
  • 1,261
  • 13
  • 8
0

I got the following code working with bootstrap 5 and react hooks

  const ref = React.useRef<HTMLDivElement>(null);
  React.useEffect(() => {
    if (ref.current) {
      ref.myModal = new bootstrap.Modal(ref.current, { backdrop: true });
    }
  }, [ref]);

to show the modal i use this code inside my component

 <button
    className="btn btn-primary"
    onClick={() => {
      if (ref.myModal) {
        ref.myModal.show();
      }
    }}
  >
    open modal
  </button>
  <div
    className="modal fade"
    tabIndex={-1}
    aria-labelledby="uploadYoutubeModalLabel"
    aria-hidden="true"
    ref={ref}
  >
    <div className="modal-dialog">bla</div>
  </div>

this binds myModel to the ref and you can call ref.myModal.show() to open the modal and ref.myModal.hide()

-1

The quickest fix would be to explicitly use the jQuery $ from the global context (which has been extended with your $.modal() because you referenced that in your script tag when you did ):

  window.$('#scheduleentry-modal').modal('show') // to show 
  window.$('#scheduleentry-modal').modal('hide') // to hide

so this is how you can about it on react

import React, { Component } from 'react';

export default Modal extends Component {
    componentDidMount() {
        window.$('#Modal').modal('show');
    }

    handleClose() {
        window.$('#Modal').modal('hide');
    }

    render() {
        <
        div className = 'modal fade'
        id = 'ModalCenter'
        tabIndex = '-1'
        role = 'dialog'
        aria - labelledby = 'ModalCenterTitle'
        data - backdrop = 'static'
        aria - hidden = 'true' >
            <
            div className = 'modal-dialog modal-dialog-centered'
        role = 'document' >
            <
            div className = 'modal-content' >
            // ...your modal body
            <
            button
        type = 'button'
        className = 'btn btn-secondary'
        onClick = {
                this.handleClose
            } >
            Close <
            /button> < /
        div > <
            /div> < /
        div >
    }
}

b0nbon
  • 96
  • 2
  • 9
-23

Just add href='#scheduleentry-modal' to the element you want to open the modal with

Or using jQuery: $('#scheduleentry-modal').modal('show');

daniula
  • 6,898
  • 4
  • 32
  • 49