Detectar la página que deja el usuario con el router de reacción

Quiero que mi aplicación ReactJS notifique a un usuario cuando navega fuera de una página específica. Específicamente, un mensaje emergente que le recuerda que debe hacer una acción:

“Los cambios se guardan, pero aún no se publican. ¿Hacer eso ahora?”

¿Debo desencadenar esto en react-router nivel mundial, o es algo que se puede hacer desde dentro de la página / componente de reacción?

No he encontrado nada en este último, y prefiero evitar el primero. A menos que sea la norma, por supuesto, pero eso me hace preguntarme cómo hacer tal cosa sin tener que agregar código a cualquier otra página posible a la que el usuario pueda acceder.

Cualquier información bienvenida, gracias!

Para react-router 2.4.0+

NOTA : es aconsejable migrar todo su código al último react-router para obtener todos los artículos nuevos.

Como se recomienda en la documentación del router de reacción :

Uno debe usar el componente de orden superior withRouter :

Creemos que este nuevo HoC es más fácil y más fácil, y lo usaremos en documentación y ejemplos, pero no es un requisito difícil cambiar.

Como un ejemplo de ES6 de la documentación:

 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 
Stuff
} }) export default withRouter(Page)

En reactjsr-enrutador v2.4.0 o superior y antes de v4 hay varias opciones

  1. Agregar función en onLeave para la Route
   
  1. Use la función setRouteLeaveHook para componentDidMount

Puede evitar que ocurra una transición o solicitar al usuario antes de salir de una ruta con un aviso de permiso.

 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?' }, // ... }) ) 

Tenga en cuenta que este ejemplo hace uso del componente de orden superior withRouter introducido en v2.4.0.

Sin embargo, esta solución no funciona del todo bien cuando se cambia manualmente la ruta en la URL

En el sentido de que

  • vemos la Confirmación – ok
  • El contenido de la página no se recarga – ok
  • La URL no cambia, no está bien

Sin embargo, en react-router v4 , es bastante más fácil de implementar con la ayuda de Prompt from’react-router

De acuerdo con la documentación

Rápido

Se usa para avisar al usuario antes de navegar desde una página. Cuando su aplicación entre en un estado que debería evitar que el usuario navegue (como un formulario está medio lleno), muestre un .

 import { Prompt } from 'react-router'  

mensaje: cadena

El mensaje para avisarle al usuario cuando intenta navegar.

  

mensaje: func

Se llamará con la siguiente ubicación y acción a la que el usuario está intentando navegar. Devuelve una cadena para mostrar un mensaje al usuario o verdadero para permitir la transición.

  ( `Are you sure you want to go to ${location.pathname}?` )}/> 

cuando: bool

En lugar de renderizar condicionalmente un detrás de un guardián, siempre puedes renderizarlo pero pasar when={true} o when={false} para prevenir o permitir la navegación en consecuencia.

En su método de representación simplemente necesita agregar esto como se menciona en la documentación de acuerdo con su necesidad.

ACTUALIZAR:

En caso de que desee tener una acción personalizada para tomar cuando el uso abandone la página, puede hacer uso del historial personalizado y configurar su enrutador como

history.js

 import createBrowserHistory from 'history/createBrowserHistory' export const history = createBrowserHistory() ... import { history } from 'path/to/history';    

y luego en tu componente puedes hacer uso de history.block like

 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 } } 

react-router v4 introduce una nueva forma de bloquear la navegación usando Prompt . Simplemente agregue esto al componente que le gustaría bloquear:

 import { Prompt } from 'react-router' const MyComponent = () => [ , {/* Component JSX */} ] 

Esto bloqueará cualquier enrutamiento, pero no actualizará ni cerrará la página. Para bloquear eso, deberá agregar esto (actualizándolo según sea necesario con el ciclo de vida React apropiado):

 componentDidUpdate = () => { if (shouldBlockNavigation) { window.onbeforeunload = () => true } else { window.onbeforeunload = undefined } } 

onbeforeunload tiene varios apoyos por parte de los navegadores.

Tuve el mismo problema en el que necesitaba un mensaje de confirmación para cualquier cambio no guardado en la página. En mi caso, estaba usando React Router v3 , por lo que no pude usar , que se introdujo desde React Router v4 .

Manejé ‘click de botón trasero’ y ‘clic de enlace accidental’ con la combinación de setRouteLeaveHook e history.pushState() , y onbeforeunload ‘reload button’ con onbeforeunload event handler.

setRouteLeaveHook ( doc ) & history.pushState ( doc )

  • Usar solo setRouteLeaveHook no fue suficiente. Por algún motivo, la URL se modificó aunque la página permaneció igual cuando se hizo clic en “botón de retroceso”.

      // 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 )

  • Se usa para manejar el botón ‘recarga accidental’

     window.onbeforeunload = this.handleOnBeforeUnload; ... handleOnBeforeUnload = e => { const message = 'Are you sure?'; e.returnValue = message; return message; } 

A continuación se muestra el componente completo que he escrito

  • tenga en cuenta que withRouter se usa para tener this.props.router .
  • tenga en cuenta que this.props.route se pasa del componente llamante
  • tenga en cuenta que currentState se pasa como prop para tener un estado inicial y para verificar cualquier cambio

     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 ( 
    {this.props.children}
    ); } } PreventRouteChange.propTypes = propTypes; export default withRouter(PreventRouteChange);

Por favor, hágamelo saber si hay alguna pregunta 🙂

Para react-router v0.13.x con react v0.13.x:

esto es posible con los métodos estáticos willTransitionTo() y willTransitionFrom() . Para versiones más nuevas, vea mi otra respuesta a continuación.

De la documentación del router de reacción :

Puede definir algunos métodos estáticos en sus manejadores de ruta que serán llamados durante las transiciones de ruta.

willTransitionTo(transition, params, query, callback)

Se invoca cuando un controlador está por renderizarse, lo que le da la oportunidad de abortar o redirigir la transición. Puede pausar la transición mientras hace algún trabajo asincrónico y llama a la callback (error) cuando haya terminado, u omita la callback en su lista de argumentos y se le llamará.

willTransitionFrom(transition, component, callback)

Se invoca cuando se está realizando una transición de una ruta activa, lo que le da la oportunidad de abortar la transición. El componente es el componente actual, probablemente lo necesite para verificar su estado y decidir si desea permitir la transición (como campos de formulario).

Ejemplo

  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(); } } } } //... }); 

Para react-router 1.0.0-rc1 con react v0.14.xo posterior:

esto debería ser posible con el routerWillLeave hook del ciclo de vida. Para versiones anteriores, vea mi respuesta más arriba.

De la documentación del router de reacción :

Para instalar este gancho, use Lifecycle Mixin en uno de sus componentes de ruta.

  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?' }, // ... }) 

Cosas. puede cambiar antes de la versión final.