Detect Route Change with react-router

前端 未结 8 917
南笙
南笙 2020-11-27 11:18

I have to implement some business logic depending on browsing history.

What I want to do is something like this:

reactRouter.onUrlChange(url => {
         


        
相关标签:
8条回答
  • 2020-11-27 11:54

    This is an old question and I don't quite understand the business need of listening for route changes to push a route change; seems roundabout.

    BUT if you ended up here because all you wanted was to update the 'page_path' on a react-router route change for google analytics / global site tag / something similar, here's a hook you can now use. I wrote it based on the accepted answer:

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

    You should use this hook once in your app, somewhere near the top but still inside a router. I have it on an App.js that looks like this:

    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>
    )
    
    0 讨论(0)
  • 2020-11-27 11:56

    React Router V5

    If you want the pathName as a string ('/' or 'users'), you can use the following:

      // React Hooks: React Router DOM
      let history = useHistory();
      const location = useLocation();
      const pathName = location.pathname;
    
    0 讨论(0)
  • 2020-11-27 12:04

    If you want to listen to the history object globally, you'll have to create it yourself and pass it to the Router. Then you can listen to it with its listen() method:

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

    Even better if you create the history object as a module, so you can easily import it anywhere you may need it (e.g. import history from './history';

    0 讨论(0)
  • 2020-11-27 12:06
    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])
    }
    
    0 讨论(0)
  • 2020-11-27 12:09

    I came across this question as I was attempting to focus the ChromeVox screen reader to the top of the "screen" after navigating to a new screen in a React single page app. Basically trying to emulate what would happen if this page was loaded by following a link to a new server-rendered web page.

    This solution doesn't require any listeners, it uses withRouter() and the componentDidUpdate() lifecycle method to trigger a click to focus ChromeVox on the desired element when navigating to a new url path.


    Implementation

    I created a "Screen" component which is wrapped around the react-router switch tag which contains all the apps screens.

    <Screen>
      <Switch>
        ... add <Route> for each screen here...
      </Switch>
    </Screen>
    

    Screen.tsx Component

    Note: This component uses 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)
    
    

    I had tried using focus() instead of click(), but click causes ChromeVox to stop reading whatever it is currently reading and start again where I tell it to start.

    Advanced note: In this solution, the navigation <nav> which inside the Screen component and rendered after the <main> content is visually positioned above the main using css order: -1;. So in pseudo code:

    <Screen style={{ display: 'flex' }}>
      <main>
      <nav style={{ order: -1 }}>
    <Screen>
    

    If you have any thoughts, comments, or tips about this solution, please add a comment.

    0 讨论(0)
  • 2020-11-27 12:12

    Update for 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>
      );
    };
    
    0 讨论(0)
提交回复
热议问题