J'ai quelques boutons qui agissent comme des itinéraires. Chaque fois que l'itinéraire est modifié, je veux m'assurer que le bouton actif change.
Existe-t-il un moyen d'écouter les changements d'itinéraire dans React Router v4?
J'ai quelques boutons qui agissent comme des itinéraires. Chaque fois que l'itinéraire est modifié, je veux m'assurer que le bouton actif change.
Existe-t-il un moyen d'écouter les changements d'itinéraire dans React Router v4?
Réponses:
J'utilise withRouter
pour obtenir l' location
accessoire. Lorsque le composant est mis à jour en raison d'un nouvel itinéraire, je vérifie si la valeur a changé:
@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>
}
}
J'espère que ça aide
withRouter
j'utilise, mais j'obtiens une erreur You should not use <Route> or withRouter() outside a <Router>
. Je ne vois aucun <Router/>
composant dans le code ci-dessus. Alors, comment ça marche?
<Switch>
composant agit en tant que routeur de facto. Seule la première <Route>
entrée à avoir un chemin correspondant sera rendue. Aucun <Router/>
composant n'est nécessaire dans ce scénario
Pour développer ce qui précède, vous devrez accéder à l'objet historique. Si vous utilisez BrowserRouter
, vous pouvez importer withRouter
et envelopper votre composant avec un composant d'ordre supérieur (HoC) afin d'avoir accès via des accessoires aux propriétés et fonctions de l'objet historique.
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);
La seule chose à savoir est que withRouter et la plupart des autres moyens d'accéder au history
semblent polluer les accessoires lorsqu'ils déstructurent l'objet en lui.
withRoutes
vous withRouter
.
Vous devez utiliser history v4 lib.
Exemple à partir de là
history.listen((location, action) => {
console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
console.log(`The last navigation action was ${action}`)
})
history.push
cela déclenche history.listen
. Consultez l'exemple Utilisation d'une URL de base dans la documentation de l' historique v4 . Comme il history
s'agit en fait d'un wrapper de l' history
objet natif d'un navigateur, il ne se comporte pas exactement comme l'objet natif.
const unlisten = history.listen(myListener); unlisten();
withRouter
, history.listen
Et useEffect
(React crochets) fonctionne très bien ensemble:
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)
Le rappel de l'écouteur se déclenchera chaque fois qu'une route est modifiée, et le retour pour history.listen
est un gestionnaire d'arrêt qui joue bien avec useEffect
.
v5.1 introduit le hook utile 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>
}
Avec crochets:
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)
Importer et rendre en tant que <DebugHistory>
composant
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]);
}
avec crochets
Avec React Hooks, j'utilise 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])
Dans certains cas, vous pouvez utiliser l' render
attribut au lieu de component
, de cette manière:
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>
}
}
Notez que si vous changez l'état de la onRouteChange
méthode, cela peut provoquer l'erreur «Profondeur de mise à jour maximale dépassée».
Avec le useEffect
hook, il est possible de détecter les changements d'itinéraire sans ajouter d'écouteur.
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);
Je viens de traiter ce problème, je vais donc ajouter ma solution en complément des autres réponses données.
Le problème ici est que useEffect
cela ne fonctionne pas vraiment comme vous le souhaiteriez, car l'appel n'est déclenché qu'après le premier rendu, il y a donc un délai indésirable.
Si vous utilisez un gestionnaire d'état comme redux, il y a de fortes chances que vous obteniez un scintillement à l'écran en raison d'un état persistant dans le magasin.
Ce que vous voulez vraiment, c'est utiliser useLayoutEffect
car cela se déclenche immédiatement.
J'ai donc écrit une petite fonction utilitaire que j'ai placée dans le même répertoire que mon routeur:
export const callApis = (fn, path) => {
useLayoutEffect(() => {
fn();
}, [path]);
};
Ce que j'appelle depuis le composant HOC comme ceci:
callApis(() => getTopicById({topicId}), path);
path
est l'accessoire qui est passé dans l' match
objet lors de l'utilisation withRouter
.
Je ne suis pas vraiment en faveur de l'écoute / désécoute manuelle de l'histoire. C'est juste imo.