We, developers know that we will work with modal a lot. My favorite library do this job is bootbox.js. Unfortunately, this library is not implemented in React & Redux. Let’s implement it by ourself.

Here is the demonstration of the app, click on “Enter your name” to show the modal.

Here is the source code of the app: https://github.com/davidtran/react-bootboxjs

We implement the confirm modal in this example. When we click on “Enter your name” button, a modal will be shown and we can type username. Upon user press enter, the modal will be hidden and hello message will be shown.

Let’s start by defining some actions.

Actions:

export const SHOW_MODAL = 'SHOW_MODAL'
export const HIDE_MODAL = 'HIDE_MODAL'
export const SET_NAME = 'SET_NAME'

export function showModal(message) {
  return {
    type: SHOW_MODAL,
    message
  }
}

export function hideModal() {
  return {
    type: HIDE_MODAL
  }
}

export function setName(name) {
  return {
    type: SET_NAME,
    name
  }
}

export function setNameAndHideModal(name) {
  return dispatch => {
    if (!name || name.trim() === '') return
    dispatch(setName(name))
    dispatch(hideModal())
  }
}

We have 4 actions in this applications: showModal, hideModalsetName and setNameAndHideModalsetNameAndHideModal is an thunk action which will fire both setName and hideModal actions. We will fire it when user click OK button on the confirm modal.

Now we write the reducers to modify the application state:

import { combineReducers } from 'redux'
import {SHOW_MODAL, HIDE_MODAL, SET_NAME} from './actions'

function modals(state = {
  isShowing: false,
  message: ''
}, action) {

  switch (action.type) {
    case SHOW_MODAL:
      return Object.assign({}, state, {
        isShowing: true,
        message: action.message
      })
    case HIDE_MODAL:
      return Object.assign({}, state, {
        isShowing: false
      })
    default:
      return state
  }

}

function name(state = null, action) {

  switch (action.type) {
    case SET_NAME:
      return action.name
    default:
      return state
  }

}

export default combineReducers({
  modals,
  name
})

The application state will look like this at runtime:

{
  modals: {
    isShowing: true,
    message: "What \'s your name?"
  },
  name: "David Tran"
}

Components:

App.js

This is the main component of our app. It listens to application state and display the hello user message. It dispatches the showModal actions when the button is clicked. We transfer the modal message and some handler functions to the ConfirmModal.

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { setNameAndHideModal, showModal, hideModal } from '../actions'
import ConfirmModal from './ConfirmModal'

class App extends Component {

  render() {
    let { showModal, onConfirm, hideModal, name } = this.props
    return (
      <div>
        <button className="btn" onClick={() => showModal("What your name?")}>Enter your name</button>
        <ConfirmModal message="'What your name?'" onConfirm={onConfirm} onCancel={hideModal}></ConfirmModal>
        { name &&
          <div className="name">
            {"Hello " + name}
          </div>
        }
      </div>
    )
  }
}

const mapStateToComponent = (state) => {
  return {
    name: state.name
  }
}

const mapDispatchToComponent = (dispatch) => {
  return {
    showModal: (message) => dispatch(showModal(message)),
    onConfirm: (name) => dispatch(setNameAndHideModal(name)),
    hideModal: () => dispatch(hideModal())
  }
}

export default connect(mapStateToComponent, mapDispatchToComponent)(App)

Usually, I would separate the presentation component and logic component but in order to keep the example short, I keep both of them in the same file.

ConfirmModal.js

This component listens to application state and reveal itself when isShowing property is set to true ( when showModal action is fire ). Beside of application state, it also receives some props from parent, including: message, onConfirm and onCancel.

When user clicks on OK button, it takes the value from text field, and calls the confirm function.

import React, { Component } from 'react'
import { connect } from 'react-redux'

class ConfirmModal extends Component {
  render() {
    let { message, onConfirm, onCancel, isShowing } = this.props

    return (
      <div className="confirm-modal">
        { isShowing &&
          <div>
            <div className="modal-backdrop"></div>
            <div className="confirm-modal-content">
              <span className="confirm-modal-message">{message}</span>
              <input className="confirm-modal-input" type="text" ref={(_ref) => this.confirmInput = _ref}/>
              <button className="btn" onClick={() => this.getTextAndConfirm()}>OK</button>
              <button className="btn" onClick={() => onCancel()}>Cancel</button>
            </div>
          </div>
        }
      </div>
    )
  }

  getTextAndConfirm() {
    let text = this.confirmInput.value
    this.props.onConfirm(text)
  }

}

const mapStateToComponent = (state) => {
  return {
    isShowing: state.modals.isShowing
  }
}

export default connect(mapStateToComponent)(ConfirmModal)

That’s how I write a a subset of Bootbox using React & Redux. We could create many types of modal like: AlertModal, YesNoModal, etc and pass modalType parameter to showModal action to display the corresponding modal.