Como ouvir as alterações de rota no roteador de reação v4?


Respostas:


170

Eu uso withRouterpara pegar o locationsuporte. Quando o componente é atualizado devido a uma nova rota, verifico se o valor mudou:

@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 que ajude


21
Use 'this.props.location.pathname' no roteador de reação v4.
ptorsson

4
@ledfusion Estou fazendo o mesmo e usando withRouter, mas estou recebendo erro You should not use <Route> or withRouter() outside a <Router>. Não vejo nenhum <Router/>componente no código acima. Então, como está funcionando?
Maverick 30/05

1
Oi @maverick. Não tenho certeza da aparência do seu código, mas no exemplo acima, o <Switch>componente está agindo como o roteador de fato. Somente a primeira <Route>entrada a ter um caminho correspondente será renderizada. Não há necessidade de qualquer <Router/>componente neste cenário
brickpop

1
para usar o @withRouter, você precisa instalar o npm install --save-dev transform-decorators-legacy
#

68

Para expandir o exposto, você precisará acessar o objeto de histórico. Se você estiver usando BrowserRouter, poderá importar withRoutere agrupar seu componente com um componente de ordem superior (HoC) para ter acesso via adereços às propriedades e funções do objeto de histórico.

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

A única coisa a ter em mente é que, com o Router e a maioria das outras maneiras de acessar, historyparece poluir os objetos à medida que desestruturam o objeto nele.


A resposta me ajudou a entender algo, independentemente da pergunta :). Mas corrigir withRoutesa withRouter.
Sergey Reutskiy

Sim, desculpe, obrigado por apontar isso. Eu alterei a postagem. Coloquei a importação correta no topo da pergunta e a escrevi incorretamente no exemplo de código.
Sam Parmenter

5
Eu acho que a versão atual do withRouter passa historyem vez da variável listen.
precisa saber é o seguinte

5
Seria bom alterar o post para demonstrar não ouvir; esse código tem um vazamento de memória.
AndrewSouthpaw

34

Você deve usar o histórico v4 lib.

Exemplo de

history.listen((location, action) => {
  console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
  console.log(`The last navigation action was ${action}`)
})

1
As chamadas history.pushState () e history.replaceState () não acionam o evento popstate, portanto, isso por si só não cobre todas as alterações de rota.
22717 Ryan

1
@ Ryan Parece que history.pushisso dispara history.listen. Consulte o exemplo Usando um URL base nos documentos da história v4 . Como esse historyé realmente um invólucro do historyobjeto nativo de um navegador, ele não se comporta exatamente como o nativo.
Rockallite 5/05

Isso parece uma solução melhor, pois muitas vezes você precisa ouvir as alterações de rota para envio de eventos, o que não está relacionado para reagir aos eventos do ciclo de vida dos componentes.
Daniel Dubovski

12
Potencial vazamento de memória! Muito importante que você faça isso! "Quando você anexa um ouvinte usando history.listen, ele retorna uma função que pode ser usada para remover o ouvinte, que pode ser chamada na lógica de limpeza:const unlisten = history.listen(myListener); unlisten();
Dehan de Croos

Vá aqui para obter documentação sobre o pacote histórico. github.com/ReactTraining/history/blob/master/docs/…
Jason Kim

26

withRouter, history.listenE useEffect(Reagir Hooks) funciona muito bem em conjunto:

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)

O retorno de chamada do ouvinte será acionado sempre que uma rota for alterada e o retorno de history.listenfor um manipulador de desligamento que funcione bem useEffect.


13

v5.1 introduz o gancho útil 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>
}

4
Apenas uma nota como eu estava tendo problemas com um erro: Cannot read property 'location' of undefined at useLocation. Você precisa garantir que a chamada useLocation () não esteja no mesmo componente que coloca o roteador na árvore: veja aqui
toddg

11

Com 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 e renderizar como <DebugHistory>componente


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]);

}

com ganchos


Holly Jesys! como funciona? Sua resposta é legal! buti coloquei o ponto do depurador em useEffect, mas sempre que alterava o nome do caminho, o local permanecia indefinido ? você pode compartilhar algum bom artigo? porque é difícil encontrar informações claras
AlexNikonov 29/03

7
import { useHistory } from 'react-router-dom';

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

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

  return null;
}

Ele também assiste alterações de hash? rota / a # 1 -> rota / a # 2
Naren

1

Com react Hooks, estou 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])

0

Em alguns casos, você pode usar o renderatributo em vez de component, desta maneira:

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

Observe que, se você alterar o estado no onRouteChangemétodo, isso poderá causar o erro 'Profundidade máxima de atualização excedida'.


0

Com o useEffectgancho, é possível detectar alterações de rota sem adicionar um ouvinte.

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

0

Como acabei de lidar com esse problema, adicionarei minha solução como um complemento às outras respostas fornecidas.

O problema aqui é que useEffectrealmente não funciona como você gostaria, pois a chamada só é acionada após a primeira renderização, para que haja um atraso indesejado.
Se você usar algum gerente de estado como o redux, é provável que ocorra uma cintilação na tela por causa do estado remanescente na loja.

O que você realmente deseja é usar, useLayoutEffectpois isso é acionado imediatamente.

Então, escrevi uma pequena função utilitária que coloquei no mesmo diretório do meu roteador:

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

Que eu chamo de dentro do componente HOC assim:

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

pathé o suporte que é passado no matchobjeto durante o uso withRouter.

Eu realmente não sou a favor de ouvir / não ouvir manualmente na história. Isso é apenas imo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.