¿Cómo escuchar los cambios de ruta en react router v4?

Respuestas:

170

Yo uso withRouterpara obtener el locationaccesorio. Cuando el componente se actualiza debido a una nueva ruta, verifico si el valor cambió:

@withRouter
class App extends React.Component {

  static propTypes = {
    location: React.PropTypes.object.isRequired
  }

  // ...

  componentDidUpdate(prevProps) {
    if (this.props.location !== prevProps.location) {
      this.onRouteChanged();
    }
  }

  onRouteChanged() {
    console.log("ROUTE CHANGED");
  }

  // ...
  render(){
    return <Switch>
        <Route path="/" exact component={HomePage} />
        <Route path="/checkout" component={CheckoutPage} />
        <Route path="/success" component={SuccessPage} />
        // ...
        <Route component={NotFound} />
      </Switch>
  }
}

Espero eso ayude

ladrillo
fuente
21
Use 'this.props.location.pathname' en react router v4.
ptorsson
44
@ledfusion Estoy haciendo lo mismo y usando withRouter, pero recibo un error You should not use <Route> or withRouter() outside a <Router>. No veo ningún <Router/>componente en el código anterior. Entonces, ¿cómo está funcionando?
disidente
1
Hola @maverick. No estoy seguro de cómo se ve su código, pero en el ejemplo anterior, el <Switch>componente está actuando como el enrutador de facto. Solo se representará la primera <Route>entrada que tenga una ruta coincidente. No hay necesidad de ningún <Router/>componente en este escenario
brickpop 05 de
1
para usar @withRouter necesita instalar npm install --save-dev transform-decorators-legacy
Sigex
68

Para ampliar lo anterior, deberá acceder al objeto de historial. Si está utilizando BrowserRouter, puede importar withRoutery envolver su componente con un componente de orden superior (HoC) para tener acceso a través de accesorios a las propiedades y funciones del objeto de historial.

import { withRouter } from 'react-router-dom';

const myComponent = ({ history }) => {

    history.listen((location, action) => {
        // location is an object like window.location
        console.log(action, location.pathname, location.state)
    });

    return <div>...</div>;
};

export default withRouter(myComponent);

Lo único que debe tener en cuenta es que con Router y la mayoría de las otras formas de acceder a él historyparecen contaminar los accesorios a medida que desestructuran el objeto en él.

Sam Parmenter
fuente
La respuesta me ayudó a entender algo independientemente de la pregunta :). Pero fijar withRoutesa withRouter.
Sergey Reutskiy
Sí, lo siento, gracias por señalarlo. He modificado la publicación. Puse la importación correcta en la parte superior de la pregunta y luego la deletreé mal en el código de ejemplo.
Sam Parmenter
55
Creo que la versión actual de withRouter pasa en historylugar de la variable listen.
mikebridge
55
Sería bueno enmendar la publicación para demostrar que no es amenazadora; Este código tiene una pérdida de memoria.
AndrewSouthpaw
34

Debes usar history v4 lib.

Ejemplo a partir de ahí

history.listen((location, action) => {
  console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
  console.log(`The last navigation action was ${action}`)
})
Sergey Reutskiy
fuente
1
Las llamadas history.pushState () e history.replaceState () no activan el evento popstate, por lo que esto solo no cubrirá todos los cambios de ruta.
Ryan
1
@ Ryan Parece que history.pushsí se dispara history.listen. Consulte el ejemplo Uso de una URL base en los documentos de la historia v4 . Debido a que en historyrealidad es un contenedor del historyobjeto nativo de un navegador, no se comporta exactamente como el nativo.
Rockallite
Esto se siente como una mejor solución, ya que muchas veces necesita escuchar los cambios de ruta para el envío de eventos, que no está relacionado con los eventos del ciclo de vida del componente de reacción.
Daniel Dubovski
12
¡Pérdida potencial de memoria! Muy importante que hagas esto! "Cuando adjuntas un oyente usando history.listen, devuelve una función que se puede usar para eliminar el oyente, que luego se puede invocar en la lógica de limpieza:const unlisten = history.listen(myListener); unlisten();
Dehan de Croos
Vaya aquí para obtener documentación sobre el paquete de historial. github.com/ReactTraining/history/blob/master/docs/…
Jason Kim
26

withRouter, history.listeny useEffect(React Hooks) funcionan muy bien juntos:

import React, { useEffect } from 'react'
import { withRouter } from 'react-router-dom'

const Component = ({ history }) => {
    useEffect(() => history.listen(() => {
        // do something on route change
        // for my example, close a drawer
    }), [])

    //...
}

export default withRouter(Component)

La devolución de llamada del oyente se activará cada vez que se cambie una ruta, y el retorno history.listenes un controlador de apagado que funciona bien con useEffect.

noetix
fuente
13

v5.1 presenta el útil gancho useLocation

https://reacttraining.com/blog/react-router-v5-1/#uselocation

import { Switch, useLocation } from 'react-router-dom'

function usePageViews() {
  let location = useLocation()

  useEffect(
    () => {
      ga.send(['pageview', location.pathname])
    },
    [location]
  )
}

function App() {
  usePageViews()
  return <Switch>{/* your routes here */}</Switch>
}
Mugen
fuente
44
Sólo una nota que yo estaba teniendo problemas con un error: Cannot read property 'location' of undefined at useLocation. Debe asegurarse de que la llamada useLocation () no esté en el mismo componente que coloca el enrutador en el árbol: consulte aquí
toddg
11

Con ganchos:

import { useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import { history as historyShape } from 'react-router-prop-types'

const DebugHistory = ({ history }) => {
  useEffect(() => {
    console.log('> Router', history.action, history.location])
  }, [history.location.key])

  return null
}

DebugHistory.propTypes = { history: historyShape }

export default withRouter(DebugHistory)

Importar y renderizar como <DebugHistory>componente

Tymek
fuente
11
import React, { useEffect } from 'react';
import { useLocation } from 'react-router';

function MyApp() {

  const location = useLocation();

  useEffect(() => {
      console.log('route has been changed');
      ...your code
  },[location.pathname]);

}

con ganchos

Sodio unido
fuente
Holly Jesys! ¿como funciona? Tu respuesta es genial! buti puse el punto del depurador en uso ¿ Efecto pero cada vez que cambié el nombre de ruta la ubicación permaneció indefinida ? ¿puedes compartir algún buen artículo? porque es difícil encontrar información clara
AlexNikonov
7
import { useHistory } from 'react-router-dom';

const Scroll = () => {
  const history = useHistory();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [history.location.pathname]);

  return null;
}
weiya ou
fuente
¿También ve cambios de hash? ruta / a # 1 -> ruta / a # 2
Naren
1

Con reaccionar ganchos, estoy usando useEffect

  const history = useHistory()
  const queryString = require('query-string')
  const parsed = queryString.parse(location.search)
  const [search, setSearch] = useState(parsed.search ? parsed.search : '')

  useEffect(() => {
    const parsedSearch = parsed.search ? parsed.search : ''
    if (parsedSearch !== search) {
      // do some action! The route Changed!
    }
  }, [location.search])
Alan
fuente
0

En algunos casos, puede usar el renderatributo en lugar de component, de esta manera:

class App extends React.Component {

    constructor (props) {
        super(props);
    }

    onRouteChange (pageId) {
        console.log(pageId);
    }

    render () {
        return  <Switch>
                    <Route path="/" exact render={(props) => { 
                        this.onRouteChange('home');
                        return <HomePage {...props} />;
                    }} />
                    <Route path="/checkout" exact render={(props) => { 
                        this.onRouteChange('checkout');
                        return <CheckoutPage {...props} />;
                    }} />
                </Switch>
    }
}

Tenga en cuenta que si cambia el estado del onRouteChangemétodo, esto podría causar el error 'Profundidad máxima de actualización excedida'.

Fernando
fuente
0

Con el useEffectenlace es posible detectar cambios de ruta sin agregar un oyente.

import React, { useEffect }           from 'react';
import { Switch, Route, withRouter }  from 'react-router-dom';
import Main                           from './Main';
import Blog                           from './Blog';


const App  = ({history}) => {

    useEffect( () => {

        // When route changes, history.location.pathname changes as well
        // And the code will execute after this line

    }, [history.location.pathname]);

    return (<Switch>
              <Route exact path = '/'     component = {Main}/>
              <Route exact path = '/blog' component = {Blog}/>
            </Switch>);

}

export default withRouter(App);
Erik Martín Jordán
fuente
0

Acabo de resolver este problema, así que agregaré mi solución como suplemento a otras respuestas dadas.

El problema aquí es que useEffectrealmente no funciona como desearía, ya que la llamada solo se activa después del primer renderizado, por lo que hay un retraso no deseado.
Si utiliza algún administrador de estado como redux, es probable que obtenga un parpadeo en la pantalla debido al estado persistente en la tienda.

Lo que realmente quieres es usar, useLayoutEffectya que esto se activa de inmediato.

Entonces escribí una pequeña función de utilidad que puse en el mismo directorio que mi enrutador:

export const callApis = (fn, path) => {
    useLayoutEffect(() => {
      fn();
    }, [path]);
};

Lo que llamo desde dentro del componente HOC como este:

callApis(() => getTopicById({topicId}), path);

pathes el accesorio que se pasa en el matchobjeto cuando se usa withRouter.

No estoy realmente a favor de escuchar / no escuchar manualmente en la historia. Eso es solo imo.

Rastro
fuente