Tenho que implementar alguma lógica de negócios dependendo do histórico de navegação.
O que eu quero fazer é algo assim:
reactRouter.onUrlChange(url => {
this.history.push(url);
});
Existe alguma maneira de receber um retorno de chamada do react-router quando o URL é atualizado?
Respostas:
Você pode usar a
history.listen()
função ao tentar detectar a mudança de rota. Considerando que você está usandoreact-router v4
, envolva seu componente comwithRouter
HOC para obter acesso aohistory
prop.history.listen()
retorna umaunlisten
função. Você usaria isso paraunregister
ouvir.Você pode configurar suas rotas como
index.js
ReactDOM.render( <BrowserRouter> <AppContainer> <Route exact path="/" Component={...} /> <Route exact path="/Home" Component={...} /> </AppContainer> </BrowserRouter>, document.getElementById('root') );
e então em AppContainer.js
class App extends Component { componentWillMount() { this.unlisten = this.props.history.listen((location, action) => { console.log("on route change"); }); } componentWillUnmount() { this.unlisten(); } render() { return ( <div>{this.props.children}</div> ); } } export default withRouter(App);
Dos documentos de história :
Quando você está usando o react-router v3, você pode fazer uso
history.listen()
dohistory
pacote como mencionado acima ou você também pode fazer usobrowserHistory.listen()
Você pode configurar e usar suas rotas como
import {browserHistory} from 'react-router'; class App extends React.Component { componentDidMount() { this.unlisten = browserHistory.listen( location => { console.log('route changes'); }); } componentWillUnmount() { this.unlisten(); } render() { return ( <Route path="/" onChange={yourHandler} component={AppContainer}> <IndexRoute component={StaticContainer} /> <Route path="/a" component={ContainerA} /> <Route path="/b" component={ContainerB} /> </Route> ) } }
fonte
react-router v4
"withRouter
history.listen()
quandowithRouter
já usa atualiza seu componente com novos props toda vez que ocorre o roteamento? Você poderia fazer uma comparação simples denextProps.location.href === this.props.location.href
emcomponentWillUpdate
para realizar qualquer coisa que você precise fazer se tiver mudado.Atualização para React Router 5.1.
import React from 'react'; import { useLocation, Switch } from 'react-router-dom'; const App = () => { const location = useLocation(); React.useEffect(() => { console.log('Location changed'); }, [location]); return ( <Switch> {/* Routes go here */} </Switch> ); };
fonte
Se você quiser ouvir o
history
objeto globalmente, terá que criá-lo e passá-lo para oRouter
. Então você pode ouvi-lo com seulisten()
método:// Use Router from react-router, not BrowserRouter. import { Router } from 'react-router'; // Create history object. import createHistory from 'history/createBrowserHistory'; const history = createHistory(); // Listen to history changes. // You can unlisten by calling the constant (`unlisten()`). const unlisten = history.listen((location, action) => { console.log(action, location.pathname, location.state); }); // Pass history to Router. <Router history={history}> ... </Router>
Melhor ainda se você criar o objeto de histórico como um módulo, para que possa importá-lo facilmente para qualquer lugar que precisar (por exemplo
import history from './history';
fonte
react-router v6
Na próxima v6 , isso pode ser feito combinando os ganchos
useLocation
euseEffect
import { useLocation } from 'react-router-dom'; const MyComponent = () => { const location = useLocation() React.useEffect(() => { // runs on location, i.e. route, change console.log('handle route change here', location) }, [location]) ... }
Para uma reutilização conveniente, você pode fazer isso em um
useLocationChange
gancho personalizado// runs action(location) on location, i.e. route, change const useLocationChange = (action) => { const location = useLocation() React.useEffect(() => { action(location) }, [location]) } const MyComponent1 = () => { useLocationChange((location) => { console.log('handle route change here', location) }) ... } const MyComponent2 = () => { useLocationChange((location) => { console.log('and also here', location) }) ... }
Se você também precisa ver a rota anterior na mudança, você pode combiná-la com um
usePrevious
ganchoconst usePrevious(value) { const ref = React.useRef() React.useEffect(() => { ref.current = value }) return ref.current } const useLocationChange = (action) => { const location = useLocation() const prevLocation = usePrevious(location) React.useEffect(() => { action(location, prevLocation) }, [location]) } const MyComponent1 = () => { useLocationChange((location, prevLocation) => { console.log('changed from', prevLocation, 'to', location) }) ... }
É importante observar que todos os itens acima são acionados na primeira rota do cliente que está sendo montada, bem como nas alterações subsequentes. Se isso for um problema, use o último exemplo e verifique se
prevLocation
existe um antes de fazer qualquer coisa.fonte
location
aqui está a localização do navegador, então é a mesma em todos os componentes e sempre correta nesse sentido. Se você usar o gancho em componentes diferentes, todos eles receberão os mesmos valores quando a localização for alterada. Eu acho que o que eles fazem com essa informação será diferente, mas é sempre consistente.Essa é uma questão antiga e não entendo muito bem a necessidade comercial de ouvir mudanças de rota para forçar uma mudança de rota; parece indireto.
MAS se você acabou aqui porque tudo o que queria era atualizar a
'page_path'
mudança de rota em um roteador react para google analytics / tag global do site / algo semelhante, aqui está um gancho que você pode usar agora. Eu o escrevi com base na resposta aceita:useTracking.js
import { useEffect } from 'react' import { useHistory } from 'react-router-dom' export const useTracking = (trackingId) => { const { listen } = useHistory() useEffect(() => { const unlisten = listen((location) => { // if you pasted the google snippet on your index.html // you've declared this function in the global if (!window.gtag) return window.gtag('config', trackingId, { page_path: location.pathname }) }) // remember, hooks that add listeners // should have cleanup to remove them return unlisten }, [trackingId, listen]) }
Você deve usar este gancho uma vez em seu aplicativo, em algum lugar próximo ao topo, mas ainda dentro de um roteador. Eu tenho em um
App.js
que se parece com este:App.js
import * as React from 'react' import { BrowserRouter, Route, Switch } from 'react-router-dom' import Home from './Home/Home' import About from './About/About' // this is the file above import { useTracking } from './useTracking' export const App = () => { useTracking('UA-USE-YOURS-HERE') return ( <Switch> <Route path="/about"> <About /> </Route> <Route path="/"> <Home /> </Route> </Switch> ) } // I find it handy to have a named export of the App // and then the default export which wraps it with // all the providers I need. // Mostly for testing purposes, but in this case, // it allows us to use the hook above, // since you may only use it when inside a Router export default () => ( <BrowserRouter> <App /> </BrowserRouter> )
fonte
Eu me deparei com essa pergunta enquanto tentava enfocar o leitor de tela ChromeVox na parte superior da "tela" após navegar para uma nova tela em um aplicativo React de página única. Basicamente tentando emular o que aconteceria se esta página fosse carregada, seguindo um link para uma nova página da web renderizada pelo servidor.
Esta solução não requer nenhum ouvinte, ela usa
withRouter()
e ocomponentDidUpdate()
método de ciclo de vida para acionar um clique para focar o ChromeVox no elemento desejado ao navegar para um novo caminho de url.Implementação
Eu criei um componente "Screen" que envolve a tag do switch react-router que contém todas as telas de aplicativos.
<Screen> <Switch> ... add <Route> for each screen here... </Switch> </Screen>
Screen.tsx
ComponenteObservação: este componente usa React + TypeScript
import React from 'react' import { RouteComponentProps, withRouter } from 'react-router' class Screen extends React.Component<RouteComponentProps> { public screen = React.createRef<HTMLDivElement>() public componentDidUpdate = (prevProps: RouteComponentProps) => { if (this.props.location.pathname !== prevProps.location.pathname) { // Hack: setTimeout delays click until end of current // event loop to ensure new screen has mounted. window.setTimeout(() => { this.screen.current!.click() }, 0) } } public render() { return <div ref={this.screen}>{this.props.children}</div> } } export default withRouter(Screen)
Tentei usar em
focus()
vez declick()
, mas clicar faz com que o ChromeVox pare de ler tudo o que está lendo no momento e comece novamente onde eu disse para começar.Nota avançada: nesta solução, a navegação
<nav>
que dentro do componente Tela e renderizada após o<main>
conteúdo é visualmente posicionada acima domain
css usandoorder: -1;
. Portanto, em pseudo código:<Screen style={{ display: 'flex' }}> <main> <nav style={{ order: -1 }}> <Screen>
Se você tiver quaisquer ideias, comentários ou dicas sobre esta solução, adicione um comentário.
fonte
Roteador React V5
Se desejar o pathName como uma string ('/' ou 'usuários'), você pode usar o seguinte:
// React Hooks: React Router DOM let history = useHistory(); const location = useLocation(); const pathName = location.pathname;
fonte
import React from 'react'; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; import Sidebar from './Sidebar'; import Chat from './Chat'; <Router> <Sidebar /> <Switch> <Route path="/rooms/:roomId" component={Chat}> </Route> </Switch> </Router>
import { useHistory } from 'react-router-dom'; function SidebarChat(props) { **const history = useHistory();** var openChat = function (id) { **//To navigate** history.push("/rooms/" + id); } }
**//To Detect the navigation change or param change** import { useParams } from 'react-router-dom'; function Chat(props) { var { roomId } = useParams(); var roomId = props.match.params.roomId; useEffect(() => { //Detect the paramter change }, [roomId]) useEffect(() => { //Detect the location/url change }, [location]) }
fonte