Rileva cambio di rotta con React-router


91

Devo implementare una logica di business a seconda della cronologia di navigazione.

Quello che voglio fare è qualcosa del genere:

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

C'è un modo per ricevere una richiamata da React-router quando l'URL viene aggiornato?


Quale versione di React-Router stai usando? Ciò determinerà l'approccio migliore. Fornirò una risposta una volta aggiornato. Detto questo, withRouter HoC è probabilmente la soluzione migliore per rendere consapevole la posizione di un componente. Aggiorna il tuo componente con nuovi ({match, history, and location}) ogni volta che cambia un percorso. In questo modo non è necessario iscriversi e annullare l'iscrizione manualmente agli eventi. Significa che è facile da usare con componenti stateless funzionali e componenti di classe.
Kyle Richardson

Risposte:


120

È possibile utilizzare la history.listen()funzione quando si cerca di rilevare il cambio di percorso. Considerando che stai usando react-router v4, avvolgi il tuo componente con withRouterHOC per ottenere l'accesso historyall'elica.

history.listen()restituisce una unlistenfunzione. Lo useresti per unregisterascoltare.

Puoi configurare i tuoi percorsi come

index.js

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

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

Dai documenti di storia :

Puoi ascoltare le modifiche alla posizione corrente utilizzando 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}`)
})

L'oggetto location implementa un sottoinsieme dell'interfaccia window.location, tra cui:

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

Le posizioni possono anche avere le seguenti proprietà:

location.state - Qualche stato extra per questa posizione che non risiede nell'URL (supportato in createBrowserHistorye createMemoryHistory)

location.key- Una stringa univoca che rappresenta questa posizione (supportata in createBrowserHistorye createMemoryHistory)

L'azione PUSH, REPLACE, or POPdipende da come l'utente è arrivato all'URL corrente.

Quando si utilizza react-router v3 è possibile utilizzare history.listen()dal historypacchetto come menzionato sopra oppure è anche possibile utilizzarlobrowserHistory.listen()

Puoi configurare e utilizzare i tuoi percorsi come

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

sta usando v3 e la seconda frase della tua risposta dice " Considerando che stai usandoreact-router v4 "
Kyle Richardson

1
@KyleRichardson Penso che tu mi abbia frainteso di nuovo, devo certamente lavorare sul mio inglese. Volevo dire che se stai usando il router react v4 e stai usando l'oggetto della cronologia, allora devi avvolgere il tuo componente conwithRouter
Shubham Khatri

@KyleRichardson, vedi la mia risposta completa, ho aggiunto modi per farlo anche nella v3. Un'altra cosa, l'OP ha commentato che sta usando la v3 oggi e ieri avevo risposto alla domanda
Shubham Khatri,

1
@ShubhamKhatri Sì, ma il modo in cui si legge la tua risposta è sbagliato. Non sta usando la v4 ... Inoltre, perché dovresti usare history.listen()quando usi withRoutergià gli aggiornamenti del tuo componente con nuovi oggetti di scena ogni volta che si verifica il routing? Potresti fare un semplice confronto di nextProps.location.href === this.props.location.hrefin componentWillUpdateper eseguire qualsiasi cosa tu debba fare se è cambiata.
Kyle Richardson

1
@ Aris, hai avuto una modifica per provarlo
Shubham Khatri

38

Aggiornamento per 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>
  );
};

14

Se vuoi ascoltare l' historyoggetto globalmente, dovrai crearlo tu stesso e passarlo al file Router. Quindi puoi ascoltarlo con il suo listen()metodo:

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

Ancora meglio se crei l'oggetto della cronologia come modulo, in modo da poterlo importare facilmente ovunque ti serva (es import history from './history';


11

react-router v6

Nella prossima v6 , questo può essere fatto combinando i ganci useLocationeuseEffect

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

Per un comodo riutilizzo, puoi farlo in un useLocationChangegancio personalizzato

// 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 hai anche bisogno di vedere il percorso precedente durante il cambio, puoi combinarlo con un usePreviousgancio

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

È importante notare che tutto quanto sopra si attiva sulla prima route client che viene montata, così come le modifiche successive. Se questo è un problema, usa l'ultimo esempio e controlla che prevLocationesista prima di fare qualsiasi cosa.


Ho una domanda. Se diversi componenti sono stati renderizzati e stanno tutti guardando useLocation, verranno attivati ​​tutti i loro useEffects. Come posso verificare che questa posizione sia corretta per il componente specifico che verrà visualizzato?
Kex

1
Ehi @ Kex, solo per chiarire locationqui c'è la posizione del browser, quindi è la stessa in ogni componente e sempre corretta in questo senso. Se utilizzi il gancio in diversi componenti, riceveranno tutti gli stessi valori quando la posizione cambia. Immagino che ciò che faranno con queste informazioni sarà diverso, ma è sempre coerente.
davnicwil

Questo ha senso. Mi chiedevo solo come un componente saprà se il cambio di posizione è rilevante per se stesso che esegue un'azione. Ad esempio, un componente riceve dashboard / elenco ma come fa a sapere se è legato a quella posizione o no?
Kex

A meno che non faccia qualcosa come if (location.pathName === "dashboard / list") {..... actions}. Tuttavia, non sembra molto elegante il percorso di codifica di un componente.
Kex

8

Questa è una vecchia domanda e non capisco bene la necessità aziendale di ascoltare i cambiamenti di percorso per spingere un cambiamento di percorso; sembra una rotonda.

MA se sei finito qui perché tutto ciò che volevi era aggiornare la modifica del percorso 'page_path'su un router di reazione per google analytics / tag globale del sito / qualcosa di simile, ecco un gancio che ora puoi usare. L'ho scritto sulla base della risposta accettata:

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

Dovresti usare questo hook una volta nella tua app, da qualche parte vicino alla parte superiore ma ancora all'interno di un router. Ce l'ho su uno App.jsche assomiglia a questo:

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

1

Mi sono imbattuto in questa domanda mentre stavo tentando di concentrare lo screen reader ChromeVox nella parte superiore dello "schermo" dopo essere passato a una nuova schermata in un'app React a pagina singola. Fondamentalmente cercando di emulare cosa accadrebbe se questa pagina venisse caricata seguendo un collegamento a una nuova pagina web renderizzata dal server.

Questa soluzione non richiede alcun listener, utilizza withRouter()e il componentDidUpdate()metodo del ciclo di vita per attivare un clic per concentrare ChromeVox sull'elemento desiderato durante la navigazione verso un nuovo percorso URL.


Implementazione

Ho creato un componente "Screen" che è avvolto attorno al tag switch switch-router che contiene tutte le schermate delle app.

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

Screen.tsx Componente

Nota: questo componente utilizza 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)

Avevo provato a usare focus()invece di click(), ma il clic fa sì che ChromeVox smetta di leggere ciò che sta leggendo attualmente e ricomincia da dove gli dico di iniziare.

Nota avanzata: in questa soluzione, la navigazione <nav>all'interno del componente Screen e renderizzata dopo il <main>contenuto è posizionata visivamente sopra il mainCSS che utilizza order: -1;. Quindi in pseudo codice:

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

Se hai pensieri, commenti o suggerimenti su questa soluzione, aggiungi un commento.


1

React Router V5

Se desideri che il pathName sia una stringa ("/" o "utenti"), puoi utilizzare quanto segue:

  // React Hooks: React Router DOM
  let history = useHistory();
  const location = useLocation();
  const pathName = location.pathname;

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])
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.