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?
Wie verwende ich eine benutzerdefinierte Komponente mit Routenübergängen zwischen Reagieren-Router?
Antwort
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)
})
}
})
}
Danke Daniel. Wollen Sie damit sagen, dass, wenn setRouteLeaveHook nur das Standard-Browser-Bestätigungsfeld verwendet, dies synchron ausgeführt wird? –
FYI, das funktioniert auch gut mit Dialog/Dialog Polyfill. –
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
Oben ist toll, außer wenn der Benutzer in der Geschichte zurückgeht. Etwas wie das Folgende sollte das Problem beheben:
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>
)
}
}
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
};
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 –