Jak słuchać zmian tras w React Router v4?

149

Mam kilka przycisków, które działają jak trasy. Za każdym razem, gdy zmienia się trasa, chcę się upewnić, że aktywny przycisk się zmienia.

Czy istnieje sposób, aby nasłuchiwać zmian tras w React Router v4?

Kasper
źródło

Odpowiedzi:

179

Używam withRouterdo zdobycia locationrekwizytu. Kiedy komponent jest aktualizowany z powodu nowej trasy, sprawdzam, czy zmieniła się wartość:

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

Mam nadzieję, że to pomoże

Brickpop
źródło
22
Użyj „this.props.location.pathname” w React router v4.
ptorsson,
4
@ledfusion Robię to samo i używam withRouter, ale pojawia się błąd You should not use <Route> or withRouter() outside a <Router>. Nie widzę żadnego <Router/>komponentu w powyższym kodzie. Jak to działa?
darKnight
1
Cześć @maverick. Nie jestem pewien, jak wygląda Twój kod, ale w powyższym przykładzie <Switch>komponent działa jako de facto router. <Route>Wyrenderowany zostanie tylko pierwszy wpis, który ma pasującą ścieżkę. W <Router/>tym scenariuszu nie jest potrzebny żaden komponent
Brickpop
1
aby korzystać z @withRouter, musisz zainstalować npm install --save-dev transform-decorators-legacy
Sigex
jak dokładnie jest z routerem.
Jovylle Bermudez
73

Aby rozwinąć powyższe, musisz dostać się do obiektu historii. Jeśli używasz BrowserRouter, możesz zaimportować withRouteri opakować swój komponent komponentem wyższego rzędu (HoC) , aby mieć dostęp poprzez właściwości do właściwości i funkcji obiektu historii.

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

Jedyną rzeczą, o której należy pamiętać, jest to, że przy użyciu routera i większości innych sposobów uzyskiwania dostępu do obiektów historyzdaje się, że zanieczyszczają one właściwości, gdy przekształcają one obiekt w jego strukturę.

Sam Parmenter
źródło
1
Odpowiedź pomogła mi coś zrozumieć bez względu na pytanie :). Ale naprawić withRoutessię withRouter.
Sergey Reutskiy
1
Tak, przepraszam, dziękuję za wskazanie tego. Poprawiłem post. Umieściłem poprawny import na górze pytania, a następnie błędnie napisałem go w przykładzie kodu.
Sam Parmenter,
5
Myślę, że obecna wersja withRouter przechodzi, historya nie zmienna listen.
mikebridge
5
Dobrze byłoby zmienić post, aby pokazać, że nie słucha; ten kod zawiera wyciek pamięci.
AndrewSouthpaw
powinieneś zapisać się tylko raz! Teraz zasubskrybujesz ponownie renderowanie każdego komponentu.
Ievgen Naida
35

Powinieneś użyć historii v4 lib.

Przykład z tego miejsca

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
źródło
1
Wywołania history.pushState () i history.replaceState () nie wyzwalają zdarzenia popstate, więc samo to nie obejmie wszystkich zmian tras.
Ryan,
1
@Ryan Wygląda na to, że history.pushwyzwala history.listen. Zobacz przykład użycia podstawowego adresu URL w dokumentach historii v4 . Ponieważ historyjest to w rzeczywistości opakowanie natywnego historyobiektu przeglądarki, nie zachowuje się dokładnie tak, jak natywny.
Rockallite
Wydaje się, że jest to lepsze rozwiązanie, ponieważ często trzeba słuchać zmian tras w celu wypychania zdarzeń, co nie jest związane z reagowaniem na zdarzenia cyklu życia komponentów.
Daniel Dubovski,
12
Potencjalny wyciek pamięci! Bardzo ważne, żebyś to zrobił! „Kiedy dołączasz odbiornik przy użyciu history.listen, zwraca on funkcję, której można użyć do usunięcia nasłuchiwania, którą można następnie wywołać w logice czyszczenia:const unlisten = history.listen(myListener); unlisten();
Dehan de Croos
Przejdź tutaj, aby uzyskać dokumentację dotyczącą pakietu historii. github.com/ReactTraining/history/blob/master/docs/…
Jason Kim,
27

withRouter, history.listeni useEffect(React Hooks) całkiem dobrze razem współpracują:

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)

Wywołanie zwrotne listenera zostanie uruchomione za każdym razem, gdy zostanie zmieniona trasa, a zwrotem dla history.listenjest program obsługi zamykania, z którym dobrze się bawi useEffect.

noetix
źródło
27

Wersja 5.1 wprowadza przydatny hak 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
źródło
10
Tylko uwaga, jak miałem problemy z błędem: Cannot read property 'location' of undefined at useLocation. Musisz się upewnić, że wywołanie useLocation () nie znajduje się w tym samym komponencie, który umieszcza router w drzewie: patrz tutaj
toddg
to niestety nie uruchamia akcji na zagnieżdżonych trasach
Eugene Kuzmenko
14
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]);

}

z haczykami

Sodium United
źródło
Holly Jesys! jak to działa? Twoja odpowiedź jest fajna! ale wprowadziłem punkt debuggera w useEffect, ale za każdym razem, gdy zmieniłem ścieżkę, lokalizacja pozostawała niezdefiniowana ? czy możesz udostępnić jakiś dobry artykuł? bo ciężko jest znaleźć jakieś jasne informacje
AlexNikonov
Użyj react-router-domzamiastreact-router
Nolesh
12

Z haczykami:

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)

Importuj i renderuj jako <DebugHistory>komponent

Tymek
źródło
7
import { useHistory } from 'react-router-dom';

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

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

  return null;
}
weiya ou
źródło
Czy obserwuje również zmiany hash? route / a # 1 -> route / a # 2
Naren
2

Korzystam z React Hooks 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
źródło
2

W przypadku komponentów funkcjonalnych spróbuj useEffect z props.location.

import React, {useEffect} from 'react';

const SampleComponent = (props) => {

      useEffect(() => {
        console.log(props.location);
      }, [props.location]);

}

export default SampleComponent;
Dilshan Liyanage
źródło
1
dzięki za to. Myślałem, że muszę spędzić godziny, aby debugować mój kod, ale tego właśnie potrzebowałem haha
giantqtipz
1

W niektórych przypadkach możesz użyć renderatrybutu zamiast component, w ten sposób:

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

Zwróć uwagę, że jeśli zmienisz stan onRouteChangemetody, może to spowodować błąd „Przekroczono maksymalną głębokość aktualizacji”.

Fernando
źródło
0

Dzięki useEffecthakowi można wykryć zmiany trasy bez dodawania nasłuchiwania.

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
źródło
0

Właśnie poradziłem sobie z tym problemem, więc dodam moje rozwiązanie jako uzupełnienie innych udzielonych odpowiedzi.

Problem polega na tym, useEffectże nie działa tak, jak byś tego chciał, ponieważ wywołanie jest wyzwalane dopiero po pierwszym renderowaniu, więc występuje niepożądane opóźnienie.
Jeśli używasz jakiegoś menedżera stanu, takiego jak redux, istnieje prawdopodobieństwo, że pojawi się migotanie na ekranie z powodu utrzymującego się stanu w sklepie.

To, czego naprawdę chcesz, to użyć useLayoutEffect ponieważ jest to uruchamiane natychmiast.

Napisałem więc małą funkcję narzędzia, którą umieściłem w tym samym katalogu co mój router:

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

Które dzwonię z poziomu HOC komponentu w ten sposób:

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

pathto właściwość, która jest przekazywana w matchobiekcie podczas używania withRouter.

Nie jestem zwolennikiem ręcznego odsłuchiwania / nieposłuchiwania historii. To tylko imo.

html_programmer
źródło