Wie kann man Routenänderungen in React Router v4 abhören?

137

Ich habe ein paar Schaltflächen, die als Routen dienen. Jedes Mal, wenn die Route geändert wird, möchte ich sicherstellen, dass sich die aktive Schaltfläche ändert.

Gibt es eine Möglichkeit, Routenänderungen in React Router v4 abzuhören?

Kasper
quelle

Antworten:

170

Ich benutze withRouter, um die locationRequisite zu bekommen . Wenn die Komponente aufgrund einer neuen Route aktualisiert wird, überprüfe ich, ob sich der Wert geändert hat:

@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>
  }
}

Ich hoffe es hilft

Brickpop
quelle
21
Verwenden Sie 'this.props.location.pathname' in React Router v4.
Ptorsson
4
@ledfusion Ich mache das gleiche und benutze withRouter, aber ich bekomme eine Fehlermeldung You should not use <Route> or withRouter() outside a <Router>. Ich sehe keine <Router/>Komponente im obigen Code. Wie funktioniert es?
Außenseiter
1
Hallo @maverick. Ich bin nicht sicher, wie Ihr Code aussieht, aber im obigen Beispiel fungiert die <Switch>Komponente als De-facto-Router. Es wird nur der erste <Route>Eintrag gerendert, der einen passenden Pfad hat. <Router/>In diesem Szenario ist keine Komponente
erforderlich
1
Um @withRouter zu verwenden, müssen Sie npm install installieren --save-dev transform-decorators-Legacy
Sigex
67

Um das oben Gesagte zu erweitern, müssen Sie zum Verlaufsobjekt gelangen. Wenn Sie verwenden BrowserRouter, können Sie withRouterIhre Komponente importieren und mit einer Komponente höherer Ordnung (HoC) umschließen , um über Requisiten auf die Eigenschaften und Funktionen des Verlaufsobjekts zugreifen zu können.

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);

Das einzige, was Sie beachten müssen, ist, dass withRouter und die meisten anderen Zugriffsmöglichkeiten historyauf die Requisiten verschmutzen, wenn sie das Objekt darin destrukturieren .

Sam Parmenter
quelle
Die Antwort hat mir geholfen, unabhängig von der Frage etwas zu verstehen :). Aber fix withRouteszu withRouter.
Sergey Reutskiy
Ja, sorry, danke, dass du darauf hingewiesen hast. Ich habe den Beitrag geändert. Ich habe den richtigen Import oben in die Frage gesetzt und ihn dann im Codebeispiel falsch geschrieben.
Sam Parmenter
5
Ich denke , die aktuelle Version von withRouter geht historyeher als die Variable listen.
Mikebridge
5
Es wäre gut, den Beitrag zu ändern, um das Abhören zu demonstrieren. Dieser Code enthält einen Speicherverlust.
AndrewSouthpaw
34

Sie sollten history v4 lib verwenden.

Beispiel von dort

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
quelle
1
Die Aufrufe history.pushState () und history.replaceState () lösen das Popstate-Ereignis nicht aus, sodass dies allein nicht alle Routenänderungen abdeckt.
Ryan
1
@ Ryan Es scheint, dass history.pushdas auslöst history.listen. Weitere Informationen finden Sie im Beispiel Verwenden einer Basis-URL in Verlauf v4-Dokumenten . Da dies historytatsächlich ein Wrapper des nativen historyObjekts eines Browsers ist, verhält es sich nicht genau wie das native.
Rockallite
Dies scheint eine bessere Lösung zu sein, da Sie häufig Routenänderungen abhören müssen, um Ereignisse zu verschieben, die nichts mit der Reaktion auf Ereignisse im Komponentenlebenszyklus zu tun haben.
Daniel Dubovski
12
Möglicher Speicherverlust! Sehr wichtig, dass Sie dies tun! "Wenn Sie einen Listener mit history.listen anhängen, wird eine Funktion zurückgegeben, mit der der Listener entfernt werden kann. const unlisten = history.listen(myListener); unlisten();
Diese
Hier finden Sie eine Dokumentation zum Verlaufspaket. github.com/ReactTraining/history/blob/master/docs/…
Jason Kim
25

withRouter, history.listenUnd useEffect(React Hooks) funktioniert zwar recht gut zusammen:

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)

Der Listener-Rückruf wird jedes Mal ausgelöst, wenn eine Route geändert wird, und die Rückgabe für history.listenist ein Shutdown-Handler, mit dem gut gespielt werden kann useEffect.

noetix
quelle
13

v5.1 führt den nützlichen Hook ein 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
quelle
4
Nur eine Anmerkung, da ich Probleme mit einem Fehler hatte : Cannot read property 'location' of undefined at useLocation. Sie müssen sicherstellen, dass sich der Aufruf von useLocation () nicht in derselben Komponente befindet, die den Router in den Baum
einfügt
11

Mit Haken:

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)

Als <DebugHistory>Komponente importieren und rendern

Tymek
quelle
10
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]);

}

mit Haken

Sodium United
quelle
Holly Jesys! wie funktioniert es? Deine Antwort ist cool! buti hat den Debugger-Punkt in useEffect gesetzt, aber jedes Mal, wenn ich den Pfadnamen geändert habe, blieb der Speicherort undefiniert ? Kannst du einen guten Artikel teilen? weil es schwierig ist, klare Informationen zu finden
AlexNikonov
7
import { useHistory } from 'react-router-dom';

const Scroll = () => {
  const history = useHistory();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [history.location.pathname]);

  return null;
}
weiya ou
quelle
Beobachtet es auch Hash-Änderungen? Route / a # 1 -> Route / a # 2
Naren
1

Mit React Hooks verwende ich 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
quelle
0

In einigen Fällen können Sie auf diese Weise ein renderAttribut anstelle von verwenden component:

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

Beachten Sie, dass beim Ändern des Status in der onRouteChangeMethode der Fehler "Maximale Aktualisierungstiefe überschritten" auftreten kann.

Fernando
quelle
0

Mit dem useEffectHook können Routenänderungen erkannt werden, ohne dass ein Listener hinzugefügt werden muss.

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
quelle
0

Ich habe mich gerade mit diesem Problem befasst, daher werde ich meine Lösung als Ergänzung zu anderen Antworten hinzufügen.

Das Problem hierbei ist, dass useEffectes nicht wirklich so funktioniert, wie Sie es möchten, da der Aufruf erst nach dem ersten Rendern ausgelöst wird und es zu einer unerwünschten Verzögerung kommt.
Wenn Sie einen Statusmanager wie Redux verwenden, besteht die Möglichkeit, dass Sie aufgrund des anhaltenden Status im Geschäft ein Flimmern auf dem Bildschirm erhalten.

Was Sie wirklich wollen, ist zu verwenden, useLayoutEffectda dies sofort ausgelöst wird.

Also habe ich ein kleines Dienstprogramm geschrieben, das ich im selben Verzeichnis wie mein Router abgelegt habe:

export const callApis = (fn, path) => {
    useLayoutEffect(() => {
      fn();
    }, [path]);
};

Was ich innerhalb der Komponente HOC so aufrufe:

callApis(() => getTopicById({topicId}), path);

pathist die Requisite, die bei der matchVerwendung im Objekt übergeben wird withRouter.

Ich bin nicht wirklich dafür, die Geschichte manuell anzuhören / abzuhören. Das ist nur imo.

Spur
quelle