Je souhaite que mon application ReactJS en informe un utilisateur lors de la navigation en dehors d'une page spécifique. Plus précisément, un message contextuel lui rappelant de faire une action:
"Les modifications sont enregistrées mais pas encore publiées. Faites-vous cela maintenant?"
Dois-je déclencher ceci sur react-router
globalement, ou est-ce quelque chose qui peut être fait à partir de la page/du composant de réaction?
Je n'ai rien trouvé sur ce dernier et je préfère éviter le premier. À moins que ce ne soit la norme bien sûr, mais je me demande comment faire une telle chose sans avoir à ajouter du code à toutes les autres pages possibles auxquelles l'utilisateur peut accéder.
Toute idée bienvenue, merci!
react-router
v4 introduit un nouveau moyen de bloquer la navigation en utilisant Prompt
. Ajoutez simplement ceci au composant que vous souhaitez bloquer:
import { Prompt } from 'react-router'
const MyComponent = () => (
<React.Fragment>
<Prompt
when={shouldBlockNavigation}
message='You have unsaved changes, are you sure you want to leave?'
/>
{/* Component JSX */}
</React.Fragment>
)
Cela bloquera tout routage, mais pas l'actualisation ou la fermeture de la page. Pour bloquer cela, vous devez ajouter ceci (en mettant à jour si nécessaire avec le cycle de vie approprié React)):
componentDidUpdate = () => {
if (shouldBlockNavigation) {
window.onbeforeunload = () => true
} else {
window.onbeforeunload = undefined
}
}
onbeforeunload supporte divers navigateurs.
Dans react-router v2.4.0
ou au-dessus et avant v4
il y a plusieurs options
<Route
path="/home"
onEnter={ auth }
onLeave={ showConfirm }
component={ Home }
>
Vous pouvez empêcher une transition ou demander à l'utilisateur avant de quitter une route avec un crochet de congé.
const Home = withRouter(
React.createClass({
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
},
routerWillLeave(nextLocation) {
// return false to prevent a transition w/o prompting the user,
// or return a string to allow the user to decide:
// return `null` or nothing to let other hooks to be executed
//
// NOTE: if you return true, other hooks will not be executed!
if (!this.state.isSaved)
return 'Your work is not saved! Are you sure you want to leave?'
},
// ...
})
)
Notez que cet exemple utilise le composant d'ordre supérieur withRouter
introduit dans v2.4.0.
Cependant, ces solutions ne fonctionnent pas parfaitement lorsque vous modifiez manuellement l'itinéraire dans une URL.
Dans le sens où
Pour react-router v4
utilisant l’invite ou l’historique personnalisé:
Cependant dans react-router v4
, c’est plutôt plus facile à mettre en oeuvre à l’aide de Prompt
from'react-router
Selon la documentation
Invite
Utilisé pour inviter l'utilisateur avant de quitter une page. Lorsque votre application entre dans un état empêchant l'utilisateur de naviguer (comme si un formulaire est à moitié rempli), restituez un
<Prompt>
.import { Prompt } from 'react-router' <Prompt when={formIsHalfFilledOut} message="Are you sure you want to leave?" />
message: chaîne de caractères
Le message à Demander à l'utilisateur lorsqu'il tente de s'éloigner.
<Prompt message="Are you sure you want to leave?"/>
message: func
Sera appelé avec le prochain emplacement et l'action que l'utilisateur tente de naviguer. Renvoie une chaîne pour afficher une invite à l'utilisateur ou true pour autoriser la transition.
<Prompt message={location => ( `Are you sure you want to go to ${location.pathname}?` )}/>
quand: bool
Au lieu de rendre sous condition un
<Prompt>
derrière un garde, vous pouvez toujours le rendre mais passerwhen={true}
ouwhen={false}
pour empêcher ou autoriser la navigation en conséquence.
Dans votre méthode de rendu, vous devez simplement ajouter ceci comme mentionné dans la documentation en fonction de vos besoins.
UPDATE:
Dans le cas où vous souhaiteriez avoir une action personnalisée à effectuer lorsque l’utilisation quitte la page, vous pouvez utiliser l’historique personnalisé et configurer votre routeur de la manière suivante:
history.js
import createBrowserHistory from 'history/createBrowserHistory'
export const history = createBrowserHistory()
...
import { history } from 'path/to/history';
<Router history={history}>
<App/>
</Router>
et puis dans votre composant, vous pouvez utiliser history.block
comme
import { history } from 'path/to/history';
class MyComponent extends React.Component {
componentDidMount() {
this.unblock = history.block(targetLocation => {
// take your action here
return false;
});
}
componentWillUnmount() {
this.unblock();
}
render() {
//component render here
}
}
Pour react-router
2.4.0 +
[~ # ~] note [~ # ~] : Il est conseillé de migrer tout votre code au plus récent react-router
pour obtenir tous les nouveaux goodies.
Comme recommandé dans le documentation de react-router :
On devrait utiliser le composant d'ordre supérieur withRouter
:
Nous pensons que cette nouvelle CdC est plus agréable et plus simple et qu’elle l’utilisera dans la documentation et les exemples, mais il n’est pas difficile de changer de fournisseur.
À titre d'exemple ES6 tiré de la documentation:
import React from 'react'
import { withRouter } from 'react-router'
const Page = React.createClass({
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, () => {
if (this.state.unsaved)
return 'You have unsaved information, are you sure you want to leave this page?'
})
}
render() {
return <div>Stuff</div>
}
})
export default withRouter(Page)
Pour react-router
V3.x
J'ai eu le même problème pour lequel j'avais besoin d'un message de confirmation pour toute modification non enregistrée sur la page. Dans mon cas, j'utilisais React Router v3 , je ne pouvais donc pas utiliser <Prompt />
, Qui avait été introduit à partir de React Router v4 .
J'ai manipulé le 'clic du bouton de retour' et le 'clic du lien accidentel' avec la combinaison de setRouteLeaveHook
et de history.pushState()
, et le 'bouton de rechargement' avec le gestionnaire d'événements onbeforeunload
.
setRouteLeaveHook ( doc ) & history.pushState ( doc )
Utiliser uniquement setRouteLeaveHook n'était pas suffisant. Pour une raison quelconque, l'URL a été modifiée même si la page est restée la même lorsque le bouton "Précédent" a été cliqué.
// setRouteLeaveHook returns the unregister method
this.unregisterRouteHook = this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
);
...
routerWillLeave = nextLocation => {
// Using native 'confirm' method to show confirmation message
const result = confirm('Unsaved work will be lost');
if (result) {
// navigation confirmed
return true;
} else {
// navigation canceled, pushing the previous path
window.history.pushState(null, null, this.props.route.path);
return false;
}
};
onbeforeunload ( doc )
Il est utilisé pour gérer le bouton de "rechargement accidentel"
window.onbeforeunload = this.handleOnBeforeUnload;
...
handleOnBeforeUnload = e => {
const message = 'Are you sure?';
e.returnValue = message;
return message;
}
Ci-dessous le composant complet que j'ai écrit
this.props.router
.this.props.route
est transmis du composant appelantnotez que currentState
est passé en tant que prop pour avoir l'état initial et pour vérifier tout changement
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { withRouter } from 'react-router';
import Component from '../Component';
import styles from './PreventRouteChange.css';
class PreventRouteChange extends Component {
constructor(props) {
super(props);
this.state = {
// initialize the initial state to check any change
initialState: _.cloneDeep(props.currentState),
hookMounted: false
};
}
componentDidUpdate() {
// I used the library called 'lodash'
// but you can use your own way to check any unsaved changed
const unsaved = !_.isEqual(
this.state.initialState,
this.props.currentState
);
if (!unsaved && this.state.hookMounted) {
// unregister hooks
this.setState({ hookMounted: false });
this.unregisterRouteHook();
window.onbeforeunload = null;
} else if (unsaved && !this.state.hookMounted) {
// register hooks
this.setState({ hookMounted: true });
this.unregisterRouteHook = this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
);
window.onbeforeunload = this.handleOnBeforeUnload;
}
}
componentWillUnmount() {
// unregister onbeforeunload event handler
window.onbeforeunload = null;
}
handleOnBeforeUnload = e => {
const message = 'Are you sure?';
e.returnValue = message;
return message;
};
routerWillLeave = nextLocation => {
const result = confirm('Unsaved work will be lost');
if (result) {
return true;
} else {
window.history.pushState(null, null, this.props.route.path);
if (this.formStartEle) {
this.moveTo.move(this.formStartEle);
}
return false;
}
};
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
PreventRouteChange.propTypes = propTypes;
export default withRouter(PreventRouteChange);
S'il vous plaît laissez-moi savoir s'il y a une question :)
Pour react-router
V0.13.x avec react
v0.13.x:
c'est possible avec les méthodes willTransitionTo()
et willTransitionFrom()
statique. Pour les versions plus récentes, voir mon autre réponse ci-dessous.
De la documentation de react-router :
Vous pouvez définir des méthodes statiques sur vos gestionnaires de routage qui seront appelées lors des transitions de routage.
willTransitionTo(transition, params, query, callback)
Appelé lorsqu'un gestionnaire est sur le point de rendre, ce qui vous permet d'abandonner ou de rediriger la transition. Vous pouvez suspendre la transition pendant que vous effectuez un travail asynchrone et appeler un rappel (erreur) lorsque vous avez terminé, ou omettre le rappel dans votre liste d'arguments et il sera appelé pour vous.
willTransitionFrom(transition, component, callback)
Appelé lors de la transition d'un itinéraire actif en sortie, ce qui vous permet d'interrompre la transition. Le composant est le composant actuel, vous en aurez probablement besoin pour vérifier son état afin de décider si vous souhaitez autoriser la transition (comme les champs de formulaire).
Exemple
var Settings = React.createClass({ statics: { willTransitionTo: function (transition, params, query, callback) { auth.isLoggedIn((isLoggedIn) => { transition.abort(); callback(); }); }, willTransitionFrom: function (transition, component) { if (component.formHasUnsavedData()) { if (!confirm('You have unsaved information,'+ 'are you sure you want to leave this page?')) { transition.abort(); } } } } //... });
Pour react-router
1.0.0-rc1 avec react
v0.14.x ou version ultérieure:
cela devrait être possible avec le hook routerWillLeave
lifecycle. Pour les versions plus anciennes, voir ma réponse ci-dessus.
De la documentation de react-router :
Pour installer ce hook, utilisez le mixin Lifecycle dans l’un de vos composants de routage.
import { Lifecycle } from 'react-router' const Home = React.createClass({ // Assuming Home is a route component, it may use the // Lifecycle mixin to get a routerWillLeave method. mixins: [ Lifecycle ], routerWillLeave(nextLocation) { if (!this.state.isSaved) return 'Your work is not saved! Are you sure you want to leave?' }, // ... })
Des choses. peut changer avant la version finale cependant.
tilisation de history.listen
Par exemple, comme ci-dessous:
Dans votre composant,
componentWillMount() {
this.props.history.listen(() => {
// Detecting, user has changed URL
console.info(this.props.history.location.pathname);
});
}
Vous pouvez utiliser cette invite.
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link, Prompt } from "react-router-dom";
function PreventingTransitionsExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Form</Link>
</li>
<li>
<Link to="/one">One</Link>
</li>
<li>
<Link to="/two">Two</Link>
</li>
</ul>
<Route path="/" exact component={Form} />
<Route path="/one" render={() => <h3>One</h3>} />
<Route path="/two" render={() => <h3>Two</h3>} />
</div>
</Router>
);
}
class Form extends Component {
state = { isBlocking: false };
render() {
let { isBlocking } = this.state;
return (
<form
onSubmit={event => {
event.preventDefault();
event.target.reset();
this.setState({
isBlocking: false
});
}}
>
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<p>
Blocking?{" "}
{isBlocking ? "Yes, click a link or the back button" : "Nope"}
</p>
<p>
<input
size="50"
placeholder="type something to block transitions"
onChange={event => {
this.setState({
isBlocking: event.target.value.length > 0
});
}}
/>
</p>
<p>
<button>Submit to stop blocking</button>
</p>
</form>
);
}
}
export default PreventingTransitionsExample;