Routenänderung mit React-Router erkennen

87

Ich muss je nach Browserverlauf eine Geschäftslogik implementieren.

Was ich tun möchte, ist ungefähr so:

reactRouter.onUrlChange(url => {
   this.history.push(url);
});

Gibt es eine Möglichkeit, einen Rückruf vom React-Router zu erhalten, wenn die URL aktualisiert wird?

Aris
quelle
Welche Version des React-Routers verwenden Sie? Das wird den besten Ansatz bestimmen. Ich werde eine Antwort geben, sobald Sie aktualisieren. Abgesehen davon ist der withRouter HoC wahrscheinlich die beste Wahl , um einen Komponentenstandort bekannt zu machen. Es aktualisiert Ihre Komponente jedes Mal, wenn sich eine Route ändert, mit einer neuen ({Übereinstimmung, Verlauf und Ort}). Auf diese Weise müssen Sie Ereignisse nicht manuell abonnieren und abbestellen. Das heißt, es ist einfach mit funktionalen zustandslosen Komponenten sowie Klassenkomponenten zu verwenden.
Kyle Richardson

Antworten:

114

Sie können die history.listen()Funktion verwenden, wenn Sie versuchen, die Routenänderung zu erkennen. Wenn Sie verwenden react-router v4, wickeln Sie Ihre Komponente mit withRouterHOC ein, um Zugriff auf die historyRequisite zu erhalten.

history.listen()gibt eine unlistenFunktion zurück. Sie würden dies verwenden, um unregisterzuzuhören.

Sie können Ihre Routen wie konfigurieren

index.js

ReactDOM.render(
      <BrowserRouter>
            <AppContainer>
                   <Route exact path="/" Component={...} />
                   <Route exact path="/Home" Component={...} />
           </AppContainer>
        </BrowserRouter>,
  document.getElementById('root')
);

und dann in 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);

Aus der Geschichte docs :

Sie können auf Änderungen am aktuellen Speicherort warten, indem Sie history.listen:

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

Das Standortobjekt implementiert eine Teilmenge der Schnittstelle window.location, einschließlich:

**location.pathname** - The path of the URL
**location.search** - The URL query string
**location.hash** - The URL hash fragment

Standorte können auch die folgenden Eigenschaften haben:

location.state - Ein zusätzlicher Status für diesen Standort, der sich nicht in der URL befindet (unterstützt in createBrowserHistoryund createMemoryHistory)

location.key- Eine eindeutige Zeichenfolge, die diesen Speicherort darstellt (unterstützt in createBrowserHistoryund createMemoryHistory)

Die Aktion hängt davon PUSH, REPLACE, or POPab, wie der Benutzer zur aktuellen URL gelangt ist.

Wenn Sie verwenden , reagieren-Router v3 können Sie Gebrauch machen history.listen()von historyPaket wie oben erwähnt , oder Sie können auch Gebrauch machenbrowserHistory.listen()

Sie können Ihre Routen wie konfigurieren und verwenden

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>
        )
    }
} 
Shubham Khatri
quelle
Er verwendet v3 und der zweite Satz Ihrer Antwort lautet "In Anbetracht dessen, dass Sie verwendenreact-router v4 "
Kyle Richardson
1
@ KyleRichardson Ich denke du hast mich wieder missverstanden, ich muss auf jeden Fall an meinem Englisch arbeiten. Ich meinte, wenn Sie withRouter
React
@KyleRichardson Wenn Sie meine vollständige Antwort sehen, habe ich auch in Version 3 Möglichkeiten hinzugefügt, dies zu tun. Eine weitere Sache, das OP kommentierte, dass er heute v3 verwendet und ich hatte gestern auf die Frage
geantwortet
1
@ShubhamKhatri Ja, aber die Art und Weise, wie Ihre Antwort lautet, ist falsch. Er ist v4 nicht mit ... Auch, warum würden Sie history.listen()bei der Verwendung von withRouterbereits aktualisiert Ihre Komponente mit neuen Requisiten tritt jedes Mal Routing? Sie können einen einfachen Vergleich von nextProps.location.href === this.props.location.hrefin durchführen componentWillUpdate, um alles auszuführen, was Sie tun müssen, wenn es sich geändert hat.
Kyle Richardson
1
@Aris, hast du eine Änderung bekommen, um es zu versuchen
Shubham Khatri
35

Update für 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>
  );
};
Onosendi
quelle
13

Wenn Sie das historyObjekt global anhören möchten , müssen Sie es selbst erstellen und an das übergeben Router. Dann können Sie es mit seiner listen()Methode anhören :

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

Noch besser, wenn Sie das Verlaufsobjekt als Modul erstellen, damit Sie es problemlos überall dort importieren können, wo Sie es benötigen (z import history from './history';

Fabian Schultz
quelle
9

react-router v6

In der kommenden Version 6 kann dies durch Kombinieren von useLocationund useEffectHooks erfolgen

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

Zur bequemen Wiederverwendung können Sie dies in einem benutzerdefinierten useLocationChangeHaken tun

// 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) 
  })
  ...
}

Wenn Sie bei Änderungen auch die vorherige Route sehen müssen, können Sie diese mit einem usePreviousHaken kombinieren

const 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) 
  })
  ...
}

Es ist wichtig zu beachten, dass alle oben genannten Feuer auf der ersten gemounteten Client-Route sowie nachfolgende Änderungen auftreten. Wenn dies ein Problem ist, verwenden Sie das letztere Beispiel und überprüfen Sie, ob ein prevLocationvorhanden ist, bevor Sie etwas unternehmen.

davnicwil
quelle
Ich habe eine Frage. Wenn mehrere Komponenten gerendert wurden und alle useLocation beobachten, werden alle ihre useEffects ausgelöst. Wie überprüfe ich, ob dieser Speicherort für die bestimmte Komponente, die angezeigt wird, korrekt ist?
Kex
1
Hey @Kex - nur um zu verdeutlichen, locationhier ist der Browser-Speicherort, also ist er in jeder Komponente gleich und in diesem Sinne immer korrekt. Wenn Sie den Hook in verschiedenen Komponenten verwenden, erhalten alle die gleichen Werte, wenn sich der Standort ändert. Ich denke, was sie mit diesen Informationen machen, wird anders sein, aber es ist immer konsistent.
Davnicwil
Das macht Sinn. Ich frage mich nur, wie eine Komponente weiß, ob die Standortänderung für sich selbst relevant ist, wenn sie eine Aktion ausführt. ZB erhält eine Komponente ein Dashboard / eine Liste, aber woher weiß sie, ob sie an diesen Speicherort gebunden ist oder nicht?
Kex
Es sei denn, ich mache so etwas wie if (location.pathName === "Dashboard / Liste") {..... Aktionen}. Es scheint jedoch kein sehr eleganter Hardcodierungspfad zu einer Komponente zu sein.
Kex
8

Dies ist eine alte Frage, und ich verstehe die geschäftliche Notwendigkeit, auf Routenänderungen zu achten, um eine Routenänderung voranzutreiben, nicht ganz. scheint Kreisverkehr.

ABER wenn Sie hier gelandet sind, weil Sie lediglich die Routenänderung 'page_path'auf einem React-Router für Google Analytics / globales Site-Tag / ähnliches aktualisieren wollten, können Sie jetzt einen Hook verwenden. Ich habe es basierend auf der akzeptierten Antwort geschrieben:

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

Sie sollten diesen Hook einmal in Ihrer App verwenden, irgendwo in der Nähe der Oberseite, aber immer noch in einem Router. Ich habe es auf einem App.js, der so aussieht:

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>
)
Johnny Magrippis
quelle
1

Ich bin auf diese Frage gestoßen, als ich versuchte, den ChromeVox-Bildschirmleser auf den oberen Rand des "Bildschirms" zu fokussieren, nachdem ich in einer React-App für einzelne Seiten zu einem neuen Bildschirm navigiert hatte. Grundsätzlich wird versucht zu emulieren, was passieren würde, wenn diese Seite geladen würde, indem Sie einem Link zu einer neuen vom Server gerenderten Webseite folgen.

Für diese Lösung sind keine Listener erforderlich. Sie verwendet withRouter()die componentDidUpdate()Lifecycle-Methode, um einen Klick auszulösen und ChromeVox auf das gewünschte Element zu fokussieren, wenn Sie zu einem neuen URL-Pfad navigieren.


Implementierung

Ich habe eine "Bildschirm" -Komponente erstellt, die um das React-Router-Switch-Tag gewickelt ist, das alle Apps-Bildschirme enthält.

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

Screen.tsx Komponente

Hinweis: Diese Komponente verwendet 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)

Ich hatte versucht, focus()anstelle von zu verwenden click(), aber durch Klicken hört ChromeVox auf zu lesen, was gerade gelesen wird, und startet erneut dort, wo ich es anweisen soll.

Erweiterter Hinweis: In dieser Lösung wird die Navigation, <nav>die innerhalb der Bildschirmkomponente angezeigt und nach dem <main>Inhalt gerendert wird, visuell über dem mainverwendeten CSS positioniert order: -1;. Also im Pseudocode:

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

Wenn Sie Gedanken, Kommentare oder Tipps zu dieser Lösung haben, fügen Sie bitte einen Kommentar hinzu.

Beau Smith
quelle
1

Reagiere auf Router V5

Wenn Sie den Pfadnamen als Zeichenfolge ('/' oder 'Benutzer') möchten, können Sie Folgendes verwenden:

  // React Hooks: React Router DOM
  let history = useHistory();
  const location = useLocation();
  const pathName = location.pathname;
jefelewis
quelle
0
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])
}
Yogesh Panchal
quelle