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

138

Eu tenho alguns botões que funcionam como rotas. Sempre que a rota é alterada, quero garantir que o botão ativo seja alterado.

Existe uma maneira de ouvir as alterações de rota no roteador de reação v4?

Kasper
fonte

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

brickpop
fonte
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.

Sam Parmenter
fonte
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}`)
})
Sergey Reutskiy
fonte
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.

noetix
fonte
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>
}
Mugen
fonte
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

Tymek
fonte
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

Sodium United
fonte
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;
}
weiya ou
fonte
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])
Alan
fonte
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'.

Fernando
fonte
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);
Erik Martín Jordán
fonte
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.

Vestígio
fonte