react-routerでルート変更を検出する


91

閲覧履歴に応じて、いくつかのビジネスロジックを実装する必要があります。

私がやりたいのは次のようなものです。

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

URLが更新されたときにreact-routerからコールバックを受信する方法はありますか?


どのバージョンのreact-routerを使用していますか?それが最善のアプローチを決定します。更新したら回答します。そうは言っても、withRouter HoCは、コンポーネントの場所を認識させるための最善の策です。ルートが変更されるたびに、コンポーネントが新しい({match、history、and location})で更新されます。これにより、イベントを手動でサブスクライブおよびサブスクライブ解除する必要がなくなります。つまり、機能的なステートレスコンポーネントやクラスコンポーネントで簡単に使用できます。
カイルリチャードソン

回答:


120

history.listen()ルート変更を検出しようとするときに機能を利用することができます。を使用していることを考慮してreact-router v4、コンポーネントをwithRouterHOCでラップして、history小道具にアクセスします。

history.listen()unlisten関数を返します。あなたはこれをunregister聞いてから使うでしょう。

次のようにルートを構成できます

index.js

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

そして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);

履歴ドキュメントから:

次を使用して、現在の場所への変更をリッスンできます 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}`)
})

ロケーションオブジェクトは、以下を含むwindow.locationインターフェイスのサブセットを実装します。

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

場所には、次のプロパティもあります。

location.state -URLに存在しないこの場所の追加の状態(createBrowserHistoryおよびで サポートされていますcreateMemoryHistory

location.key-この場所を表す一意の文字列(createBrowserHistoryおよびでサポートされていますcreateMemoryHistory

アクションはPUSH, REPLACE, or POP、ユーザーが現在のURLにアクセスした方法に応じたものです。

react-router v3を使用している場合はhistory.listen()history上記のようにパッケージから使用することも、使用することもできます。browserHistory.listen()

次のようなルートを構成して使用できます

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

彼はV3を使用していますし、あなたの答えの2番目の文が「言うあなたが使用している考えるreact-router v4
カイル・リチャードソン

1
@KyleRichardsonあなたは私をもう一度誤解したと思います、私は確かに私の英語に取り組む必要があります。つまり、react-router v4を使用していて、履歴オブジェクトを使用している場合は、コンポーネントを次のようにラップする必要がありますwithRouter
Shubham Khatri 2017

@KyleRichardson完全な答えが表示されますが、v3でもそれを行う方法を追加しました。もう1つ、OPは、彼が今日v3を使用しているとコメントし、昨日質問に答えました
Shubham Khatri 2017

1
@ShubhamKhatriはい、しかしあなたの答えの読み方は間違っています。彼はv4を使用history.listen()してwithRouterいません...また、ルーティングが発生するたびに、コンポーネントを新しい小道具で既に更新している場合、なぜ使用するのでしょうか。nextProps.location.href === this.props.location.hrefincomponentWillUpdateを簡単に比較して、変更された場合に必要なことをすべて実行できます。
カイルリチャードソン

1
@Aris、試してみるための変更はありましたか
Shubham Khatri 2017

38

Reactルーター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

historyオブジェクトをグローバルにリッスンする場合は、自分でオブジェクトを作成して、に渡す必要がありRouterます。次に、そのlisten()方法でそれを聞くことができます:

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

履歴オブジェクトをモジュールとして作成するとさらに良いので、必要な場所に簡単にインポートできます(例: import history from './history';


11

react-router v6

今後のv6では、これはuseLocationuseEffectフックを組み合わせることで実行できます。

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

便利な再利用のために、カスタムuseLocationChangeフックでこれを行うことができます

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

変更時に前のルートも確認する必要がある場合は、usePreviousフックと組み合わせることができます

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

マウントされている最初のクライアントルートで上記のすべてが発生し、その後の変更に注意することが重要です。それが問題である場合は、後者の例を使用して、prevLocation何かを行う前にが存在することを確認してください。


質問があります。複数のコンポーネントがレンダリングされ、それらがすべてuseLocationを監視している場合、それらのすべてのuseEffectsがトリガーされます。この場所が表示される特定のコンポーネントに対して正しいことを確認するにはどうすればよいですか?
のKex

1
ちょっと@ Kex-locationここで明確にするためにブラウザの場所があるので、それはすべてのコンポーネントで同じであり、その意味で常に正しいです。異なるコンポーネントでフックを使用する場合、場所が変更されたときにそれらはすべて同じ値を受け取ります。彼らがその情報をどうするかは違うと思いますが、それは常に一貫しています。
davnicwil

それは理にかなっている。コンポーネントが、場所の変更がアクションの実行に関連しているかどうかをどのように知るのか疑問に思っています。たとえば、コンポーネントはダッシュボード/リストを受け取りますが、その場所に関連付けられているかどうかをどのように知るのですか?
のKex

if(location.pathName ===“ dashboard / list”){.....アクション}のようなことをしない限り。ただし、コンポーネントへのハードコーディングパスはあまり洗練されていないようです。
のKex

8

これは古い質問であり、ルート変更をプッシュするためにルート変更をリッスンするビジネスニーズを完全には理解していません。ラウンドアバウトのようです。

しかし'page_path'、グーグルアナリティクス/グローバルサイトタグ/同様のもののreact-routerルート変更を更新することだけが必要だったためにここにたどり着いた場合は、ここで使用できるフックがあります。私は受け入れられた答えに基づいてそれを書きました:

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

このフックは、アプリの上部近くにあるがルーター内で1回使用する必要があります。私はそれApp.jsを次のようにしています:

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

Reactシングルページアプリで新しい画面に移動した後、ChromeVoxスクリーンリーダーを「画面」の上部にフォーカスしようとしたときに、この質問に遭遇しました。基本的に、サーバーでレンダリングされた新しいWebページへのリンクをたどって、このページが読み込まれた場合に何が起こるかをエミュレートしようとします。

このソリューションはリスナーを必要とせずwithRouter()componentDidUpdate()ライフサイクルメソッドを使用してクリックをトリガーし、新しいURLパスに移動するときにChromeVoxを目的の要素にフォーカスします。


実装

すべてのアプリ画面を含むreact-routerスイッチタグをラップする「Screen」コンポーネントを作成しました。

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

Screen.tsx 成分

注:このコンポーネントは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)

focus()代わりにを使用してみましたclick()が、クリックするとChromeVoxは現在読み取っている内容の読み取りを停止し、開始するように指示したところから再開します。

高度な注意:このソリューションでは<nav>、Screenコンポーネント内で<main>コンテンツの後にレンダリングされるナビゲーションは、maincssを使用して視覚的に上に配置されますorder: -1;。したがって、擬似コードでは:

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

このソリューションについての考え、コメント、またはヒントがある場合は、コメントを追加してください。


1

ReactルーターV5

pathNameを文字列(「/」または「users」)として使用する場合は、次を使用できます。

  // 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])
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.