2016-02-08 4 views
9

Der Artikel Confirming Navigation erläutert, wie Sie eine Browserbestätigungsbox in Ihrem Übergangshaken verwenden. Fein. Aber ich möchte mein eigenes Dialogfeld verwenden. Wenn ich die Methoden aus dem history Modul verwenden würde, denke ich, dass das möglich ist. Ist es möglich, dies mit dem setRouteLeaveHook in react-Router zu tun?Wie verwende ich eine benutzerdefinierte Komponente mit Routenübergängen zwischen Reagieren-Router?

+0

Der Vollständigkeit halber: Das ist die Verbindung, die zeigt, wie ein benutzerdefiniertes Dialogfeld hinzuzufügen, wenn die Geschichte Modul. https://github.com/mjackson/history/blob/master/docs/ConfirmingNavigation.md –

Antwort

14

Das Kernproblem ist, dass setRouteLeaveHook erwartet, dass die Hook-Funktion sein Ergebnis synchron zurückgibt. Das bedeutet, dass Sie nicht die Zeit haben, eine benutzerdefinierte Dialogkomponente anzuzeigen, zu warten, bis der Benutzer auf eine Option klickt, und dann das Ergebnis zurückgibt. Also brauchen wir eine Möglichkeit, einen asynchronen Haken zu spezifizieren. Hier ist eine Utility-Funktion schrieb ich:

// Asynchronous version of `setRouteLeaveHook`. 
// Instead of synchronously returning a result, the hook is expected to 
// return a promise. 
function setAsyncRouteLeaveHook(router, route, hook) { 
    let withinHook = false 
    let finalResult = undefined 
    let finalResultSet = false 
    router.setRouteLeaveHook(route, nextLocation => { 
    withinHook = true 
    if (!finalResultSet) { 
     hook(nextLocation).then(result => { 
     finalResult = result 
     finalResultSet = true 
     if (!withinHook && nextLocation) { 
      // Re-schedule the navigation 
      router.push(nextLocation) 
     } 
     }) 
    } 
    let result = finalResultSet ? finalResult : false 
    withinHook = false 
    finalResult = undefined 
    finalResultSet = false 
    return result 
    }) 
} 

Hier ist ein Beispiel dafür, wie es zu benutzen, vex mit einem Dialogfeld anzuzeigen:

componentWillMount() { 
    setAsyncRouteLeaveHook(this.context.router, this.props.route, this.routerWillLeave) 
} 
​ 
routerWillLeave() { 
    return new Promise((resolve, reject) => { 
    if (!this.state.textValue) { 
     // No unsaved changes -- leave 
     resolve(true) 
    } else { 
     // Unsaved changes -- ask for confirmation 
     vex.dialog.confirm({ 
     message: 'There are unsaved changes. Leave anyway?' + nextLocation, 
     callback: result => resolve(result) 
     }) 
    } 
    }) 
} 
+0

Danke Daniel. Wollen Sie damit sagen, dass, wenn setRouteLeaveHook nur das Standard-Browser-Bestätigungsfeld verwendet, dies synchron ausgeführt wird? –

+0

FYI, das funktioniert auch gut mit Dialog/Dialog Polyfill. –

+0

Danke für die Lösung, ich frage mich, ob 'router.push (nextLocation)' immer funktionieren würde, egal ob der Benutzer auf die Zurück- oder Weiter-Taste klickt. Es scheint, als würde 'push' nur eine Richtung bedeuten, aber vielleicht irre ich mich. – majorBummer

0

Oben ist toll, außer wenn der Benutzer in der Geschichte zurückgeht. Etwas wie das Folgende sollte das Problem beheben:

0

Hier ist meine Lösung für die gleiche. Ich habe eine benutzerdefinierte Dialogkomponente erstellt, mit der Sie jede Komponente in Ihrer App umbrechen können. Sie können Ihre Kopfzeile umbrechen und auf diese Weise auf allen Seiten präsentieren. Es geht davon aus, dass Sie Redux-Formular verwenden, aber Sie können areThereUnsavedChanges einfach durch einen anderen Formularänderungs-Prüfcode ersetzen. Es verwendet auch React Bootstrap modal, das Sie wiederum durch Ihren eigenen benutzerdefinierten Dialog ersetzen können.

import React, { Component } from 'react' 
import { connect } from 'react-redux' 
import { withRouter, browserHistory } from 'react-router' 
import { translate } from 'react-i18next' 
import { Button, Modal, Row, Col } from 'react-bootstrap' 

// have to use this global var, because setState does things at unpredictable times and dialog gets presented twice 
let navConfirmed = false 

@withRouter 
@connect(
    state => ({ form: state.form }) 
) 
export default class UnsavedFormModal extends Component { 
    constructor(props) { 
    super(props) 
    this.areThereUnsavedChanges = this.areThereUnsavedChanges.bind(this) 
    this.state = ({ unsavedFormDialog: false }) 
    } 

    areThereUnsavedChanges() { 
    return this.props.form && Object.values(this.props.form).length > 0 && 
     Object.values(this.props.form) 
     .findIndex(frm => (Object.values(frm) 
      .findIndex(field => field && field.initial && field.initial !== field.value) !== -1)) !== -1 
    } 

    render() { 
    const moveForward =() => { 
     this.setState({ unsavedFormDialog: false }) 
     navConfirmed = true 
     browserHistory.push(this.state.nextLocation.pathname) 
    } 
    const onHide =() => this.setState({ unsavedFormDialog: false }) 

    if (this.areThereUnsavedChanges() && this.props.router && this.props.routes && this.props.routes.length > 0) { 
     this.props.router.setRouteLeaveHook(this.props.routes[this.props.routes.length - 1], (nextLocation) => { 
     if (navConfirmed || !this.areThereUnsavedChanges()) { 
      navConfirmed = false 
      return true 
     } else { 
      this.setState({ unsavedFormDialog: true, nextLocation: nextLocation }) 
      return false 
     } 
     }) 
    } 

    return (
     <div> 
     {this.props.children} 
     <Modal show={this.state.unsavedFormDialog} onHide={onHide} bsSize="sm" aria-labelledby="contained-modal-title-md"> 
      <Modal.Header> 
      <Modal.Title id="contained-modal-title-md">WARNING: unsaved changes</Modal.Title> 
      </Modal.Header> 
      <Modal.Body> 
      Are you sure you want to leave the page without saving changes to the form? 
      <Row> 
       <Col xs={6}><Button block onClick={onHide}>Cancel</Button></Col> 
       <Col xs={6}><Button block onClick={moveForward}>OK</Button></Col> 
      </Row> 
      </Modal.Body> 
     </Modal> 
     </div> 
    ) 
    } 
} 
0

Ich habe es durch das Setzen eines boolean auf Zustand arbeiten, ob Sie weg zu navigieren bestätigt haben (reagieren-Router 2.8.x verwenden). Wie es in den Link sagt geschrieben Sie: https://github.com/ReactTraining/react-router/blob/master/docs/guides/ConfirmingNavigation.md

return false einen Übergang w/o Aufforderung des Benutzers

jedoch zu verhindern, sie vergessen zu erwähnen, dass der Haken als auch nicht registrierte sein sollte, siehe here und here.

Damit können wir unsere eigene Lösung implementieren, wie folgt:

class YourComponent extends Component { 
    constructor() { 
    super(); 

    const {route} = this.props; 
    const {router} = this.context; 

    this.onCancel = this.onCancel.bind(this); 
    this.onConfirm = this.onConfirm.bind(this); 

    this.unregisterLeaveHook = router.setRouteLeaveHook(
     route, 
     this.routerWillLeave.bind(this) 
    ); 
    } 

    componentWillUnmount() { 
    this.unregisterLeaveHook(); 
    } 

    routerWillLeave() { 
    const {hasConfirmed} = this.state; 
    if (!hasConfirmed) { 
     this.setState({showConfirmModal: true}); 

     // Cancel route change 
     return false; 
    } 

    // User has confirmed. Navigate away 
    return true; 
    } 

    onCancel() { 
    this.setState({showConfirmModal: false}); 
    } 

    onConfirm() { 
    this.setState({hasConfirmed: true, showConfirmModal: true}, function() { 
     this.context.router.goBack(); 
    }.bind(this)); 
    } 

    render() { 
    const {showConfirmModal} = this.state; 

    return (
     <ConfirmModal 
     isOpen={showConfirmModal} 
     onCancel={this.onCancel} 
     onConfirm={this.onConfirm} /> 
    ); 
    } 
} 

YourComponent.contextTypes = { 
    router: routerShape 
};