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 withRouterpour obtenir l' locationaccessoire. 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
withRouterj'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 withRouteret 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 historysemblent polluer les accessoires lorsqu'ils déstructurent l'objet en lui.
withRoutesvous 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.pushcela déclenche history.listen. Consultez l'exemple Utilisation d'une URL de base dans la documentation de l' historique v4 . Comme il historys'agit en fait d'un wrapper de l' historyobjet natif d'un navigateur, il ne se comporte pas exactement comme l'objet natif.
const unlisten = history.listen(myListener); unlisten();
withRouter, history.listenEt 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.listenest 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' renderattribut 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 onRouteChangeméthode, cela peut provoquer l'erreur «Profondeur de mise à jour maximale dépassée».
Avec le useEffecthook, 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 useEffectcela 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 useLayoutEffectcar 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);
pathest l'accessoire qui est passé dans l' matchobjet 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.