Bagaimana cara mendengarkan perubahan rute di router reaksi v4?

138

Saya memiliki beberapa tombol yang berfungsi sebagai rute. Setiap kali rute diubah, saya ingin memastikan tombol yang aktif berubah.

Apakah ada cara untuk mendengarkan perubahan rute pada router reaksi v4?

Kasper
sumber

Jawaban:

170

Saya gunakan withRouteruntuk mendapatkan locationprop. Ketika komponen diperbarui karena rute baru, saya memeriksa apakah nilainya berubah:

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

Semoga ini bisa membantu

brickpop
sumber
21
Gunakan 'this.props.location.pathname' di react router v4.
ptorsson
4
@ ledfusion Saya melakukan hal yang sama dan menggunakan withRouter, tetapi saya mendapatkan kesalahan You should not use <Route> or withRouter() outside a <Router>. Saya tidak melihat <Router/>komponen dalam kode di atas. Jadi bagaimana cara kerjanya?
maverick
1
Hai @maverick. Saya tidak yakin bagaimana bentuk kode Anda, tetapi dalam contoh di atas, <Switch>komponen tersebut bertindak sebagai router de-facto. Hanya <Route>entri pertama yang memiliki jalur yang cocok, akan diberikan. Tidak perlu <Router/>komponen apa pun dalam skenario ini
brickpop
1
untuk menggunakan @withRouter Anda perlu menginstal npm install --save-dev transform-decorators-legacy
Sigex
68

Untuk memperluas di atas, Anda harus mendapatkan objek sejarah. Jika Anda menggunakan BrowserRouter, Anda dapat mengimpor withRouterdan membungkus komponen Anda dengan komponen orde tinggi (HoC) agar memiliki akses melalui alat peraga ke properti dan fungsi objek sejarah.

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

Satu-satunya hal yang perlu diperhatikan adalah bahwa denganouter dan sebagian besar cara lain untuk mengakses historytampaknya mencemari alat peraga ketika mereka de-struktur objek ke dalamnya.

Sam Parmenter
sumber
Jawabannya membantu saya memahami sesuatu terlepas dari pertanyaannya :). Tapi memperbaiki withRoutesuntuk withRouter.
Sergey Reutskiy
Yap, maaf, terima kasih sudah menunjukkannya. Saya telah mengubah pos. Saya meletakkan impor yang benar di bagian atas pertanyaan dan kemudian salah mengeja dalam contoh kode.
Sam Parmenter
5
Saya pikir versi withRouter yang lewat historybukan variabel listen.
mikebridge
5
Akan baik untuk mengubah posting untuk menunjukkan tidak mendengarkan; kode ini memiliki kebocoran memori di dalamnya.
AndrewSouthpaw
34

Anda harus menggunakan riwayat v4 lib.

Contoh dari sana

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
sumber
1
Panggilan history.pushState () dan history.replaceState () tidak memicu peristiwa popstate, jadi ini saja tidak akan mencakup semua perubahan rute.
Ryan
1
@Ryan Tampaknya itu history.pushmemicu history.listen. Lihat contoh Menggunakan URL Pangkalan di riwayat v4 docs . Karena itu historysebenarnya pembungkus historyobjek asli browser, itu tidak berperilaku persis seperti yang asli.
Rockallite
Ini terasa seperti solusi yang lebih baik, karena sering kali Anda perlu mendengarkan perubahan rute untuk acara mendorong, yang tidak terkait untuk bereaksi peristiwa siklus hidup komponen.
Daniel Dubovski
12
Kebocoran memori potensial! Sangat penting bagi Anda untuk melakukan ini! "Ketika Anda melampirkan pendengar menggunakan history.listen, itu mengembalikan fungsi yang dapat digunakan untuk menghapus pendengar, yang kemudian dapat dipanggil dalam logika pembersihan:const unlisten = history.listen(myListener); unlisten();
Dehan de Croos
Buka di sini untuk dokumentasi tentang paket sejarah. github.com/ReactTraining/history/blob/master/docs/…
Jason Kim
26

withRouter,, history.listendan useEffect(React Hooks) bekerja sangat baik bersama-sama:

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)

Panggilan balik pendengar akan diaktifkan setiap kali rute diubah, dan pengembaliannya history.listenadalah penangan shutdown yang bekerja dengan baik useEffect.

noetix
sumber
13

v5.1 memperkenalkan kait berguna useLocation

https://reacttraining.com/blog/react-router-v5-1/#uselokasi

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
sumber
4
Hanya sebuah catatan seperti aku sedang mengalami kesulitan dengan kesalahan: Cannot read property 'location' of undefined at useLocation. Anda perlu memastikan bahwa panggilan useLocation () tidak berada di komponen yang sama dengan yang menempatkan router ke pohon: lihat di sini
toddg
11

Dengan kait:

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)

Impor dan render sebagai <DebugHistory>komponen

Tymek
sumber
11
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]);

}

dengan kait

Sodium United
sumber
Holly Jesys! bagaimana cara kerjanya? Jawaban Anda keren! buti meletakkan titik debugger di useEffect tetapi setiap kali saya mengubah pathname lokasi tetap tidak ditentukan ? dapatkah Anda membagikan artikel yang bagus? karena sulit untuk menemukan informasi yang jelas
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
sumber
Apakah itu juga menonton perubahan hash? route / a # 1 -> route / a # 2
Naren
1

Dengan Hooks bereaksi, saya menggunakan 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
sumber
0

Dalam beberapa kasus, Anda mungkin menggunakan renderatribut alih-alih component, dengan cara ini:

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

Perhatikan bahwa jika Anda mengubah status dalam onRouteChangemetode, ini dapat menyebabkan kesalahan 'Kedalaman pembaruan maksimum terlampaui'.

Fernando
sumber
0

Dengan useEffectkait itu mungkin untuk mendeteksi perubahan rute tanpa menambahkan pendengar.

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

Saya hanya berurusan dengan masalah ini, jadi saya akan menambahkan solusi saya sebagai suplemen pada jawaban lain yang diberikan.

Masalahnya di sini adalah bahwa useEffecttidak benar-benar berfungsi seperti yang Anda inginkan, karena panggilan hanya akan dipicu setelah render pertama sehingga ada penundaan yang tidak diinginkan.
Jika Anda menggunakan beberapa pengelola negara seperti redux, kemungkinan Anda akan mendapatkan layar berkedip karena keadaan yang tersisa di toko.

Apa yang benar-benar Anda inginkan adalah menggunakan useLayoutEffectkarena ini akan segera dipicu.

Jadi saya menulis fungsi utilitas kecil yang saya taruh di direktori yang sama dengan router saya:

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

Yang saya panggil dari dalam komponen HOC seperti ini:

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

pathadalah penyangga yang dilewatkan di matchobjek saat menggunakan withRouter.

Saya tidak terlalu suka mendengarkan / tidak mendengarkan secara manual tentang sejarah. Itu hanya imo.

Jejak
sumber