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 lá

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.