Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confirming navigation with with custom dialog using setRouteLeaveHook

I am trying to use a custom dialog to ask a user for confirmation before navigating away with unsaved data.

Following the docs I have:

  componentDidMount() {
      this.props.router.setRouteLeaveHook(
        this.props.route,
        this.routerWillLeave
      )
  }

But instead of

  routerWillLeave(nextLocation) {
    if (!this.props.pristine) {
      return 'You have unsaved information, are you sure you want to leave this page?'

    }

I have

  routerWillLeave(nextLocation) {
    if (!this.props.pristine) {
        this.setState({open: true})
        this.forceUpdatate() //necessary or else render won't be called to open dialog
    }

The dialog component I am using comes from material-ui which just expects an open boolean to control the dialog, it also takes a handleCancel and handleContinue methods, but I am not sure how to hook it up with routerWillLeave.

The handleCancel method is simple as it just closes the dialog:

  handleCancel() {
    this.setState({open: false})
  };

I have wrapped the dialog component in component called Notification

export default class Notification extends React.Component {

  render() {

   const { open, handleCancel, handleContinue } = this.props

    const actions = [
      <FlatButton
        label="Cancel"
        primary={true}
        onTouchTap={handleCancel}
      />,
      <FlatButton
        label="Continue"
        primary={true}
        onTouchTap={handleContinue}
      />,
    ];

    return (
      <div>
        <Dialog
          actions={actions}
          modal={false}
          open={open}

        >
          You have unsaved data. Discard changes?
        </Dialog>
      </div>
    );
  }
}

And I can call it from the parent component, I have this in the render method:

<Notification open={open} handleCancel={this.handleCancel} handleContinue={this.handleContinue}/>

Basically my question is how can I wire this up with routerWillLeave instead of showing the native browser alert?

like image 358
chefcurry7 Avatar asked Jan 05 '23 00:01

chefcurry7


1 Answers

When you call createHistory, one of the options to it is getUserConfirmation, which takes a prompt message and a callback. For DOM histories (browserHistory and hashHistory), getUserConfirmation calls window.confirm, passing it the message. The callback function receives the return value of window.confirm [0].

What you need to do is to provide your own getUserConfirmation method that replicates window.confirm. When it gets called, you should display your modal and trigger the callback depending on which button is clicked.

Notification.js

The <Notification> component should take the prompt message and the callback function to call based on the user's action.

class Notification extends React.Component {

  contructor(props) {
    this.state = {
      open: false
    }
  }

  handleCancel() {
    this.props.callback(false)
    this.setState({ open: false })
  }

  handleContinue() {
    this.props.callback(true)
    this.setState({ open: false })
  }

  render() {
    const { message } = this.props
    const { open } = this.state
    const actions = [
      <FlatButton
        label="Cancel"
        primary={true}
        onTouchTap={this.handleCancel.bind(this)}
      />,
      <FlatButton
        label="Continue"
        primary={true}
        onTouchTap={this.handleContinue.bind(this)}
      />,
    ];

    return (
      <div>
        <Dialog
          actions={actions}
          modal={true}
          open={open}
        >
          {message}
        </Dialog>
      </div>
    );
  }
}

ModalConfirmation.js

The confirmation modal isn't really a part of your UI, which is why I am rendering it in a separate render process than the rest of the app.

import React from 'react'
import ReactDOM from 'react-dom'
import Notification from './components/Notification'

export default function = (holderID) => {
  var modalHolder = document.getElementById(holderID)

  return function ModalUserConfirmation(message, callback) {
    ReactDOM.render((
      <Notification open={true} message={message} callback={callback} />
    ), modalHolder)
  }
}

This will obviously force you to create your own history object. You cannot just import browserHistory or hashHistory because those use window.confirm. Luckily it is trivial to create your own history. This is essentially the same code as is used in browserHistory [1], but it passes createBrowserHistory your getUserConfirmation function.

createConfirmationHistory.js

import createBrowserHistory from 'history/lib/createBrowserHistory'
import createRouterHistory from './createRouterHistory'

export default function(getUserConfirmation) {
  return createRouterHistory(createBrowserHistory({
    getUserConfirmation
  })
}

index.js

Finally, you need to put this all together.

import createHistory from './createConfirmationHistory'
import ModalConfirmation from './ModalConfirmation'

const getModalConfirmation = ModalConfirmation('modal-holder')
const history = createHistory(getModalConfirmation)

ReactDOM.render((
  <Router history={history}>
    // ...
  </Router>
), document.getElementById('root')

You would have to refactor this a bit if you wanted to use a history singleton, but otherwise it should work. (I haven't actually tested it, though).

[0] https://github.com/mjackson/history/blob/v2.x/modules/DOMUtils.js#L38-L40

[1] https://github.com/ReactTraining/react-router/blob/master/modules/browserHistory.js

like image 123
Paul S Avatar answered Jan 13 '23 10:01

Paul S