useRouter
Si vous souhaitez accéder à l'objet router
dans n'importe quel composant fonction de votre application, vous pouvez utiliser le hook useRouter
. Voici un exemple :
import { useRouter } from 'next/router'
function ActiveLink({ children, href }) {
const router = useRouter()
const style = {
marginRight: 10,
color: router.asPath === href ? 'red' : 'black',
}
const handleClick = (e) => {
e.preventDefault()
router.push(href)
}
return (
<a href={href} onClick={handleClick} style={style}>
{children}
</a>
)
}
export default ActiveLink
useRouter
est un Hook React, ce qui signifie qu'il ne peut pas être utilisé avec des classes. Vous pouvez soit utiliser withRouter, soit encapsuler votre classe dans un composant fonction.
Objet router
Voici la définition de l'objet router
retourné à la fois par useRouter
et withRouter
:
pathname
:String
- Le chemin du fichier de route actuel qui vient après/pages
. Par conséquent,basePath
,locale
et la barre oblique finale (trailingSlash: true
) ne sont pas inclus.query
:Object
- La chaîne de requête analysée en objet, incluant les paramètres des routes dynamiques. Ce sera un objet vide pendant le prérendu si la page n'utilise pas le rendu côté serveur (SSR). Par défaut :{}
asPath
:String
- Le chemin tel qu'affiché dans le navigateur, incluant les paramètres de recherche et respectant la configurationtrailingSlash
.basePath
etlocale
ne sont pas inclus.isFallback
:boolean
- Indique si la page actuelle est en mode de secours (fallback).basePath
:String
- Le basePath actif (si activé).locale
:String
- La locale active (si activée).locales
:String[]
- Toutes les locales supportées (si activées).defaultLocale
:String
- La locale par défaut actuelle (si activée).domainLocales
:Array<{domain, defaultLocale, locales}>
- Toutes les locales configurées par domaine.isReady
:boolean
- Indique si les champs du routeur sont mis à jour côté client et prêts à être utilisés. Ne doit être utilisé qu'à l'intérieur de méthodesuseEffect
et pas pour un rendu conditionnel côté serveur. Voir la documentation associée pour les cas d'utilisation avec les pages optimisées statiquement automatiquementisPreview
:boolean
- Indique si l'application est actuellement en mode prévisualisation.
L'utilisation du champ
asPath
peut entraîner une incohérence entre le client et le serveur si la page est rendue côté serveur ou avec l'optimisation statique automatique. Évitez d'utiliserasPath
tant que le champisReady
n'est pastrue
.
Les méthodes suivantes sont incluses dans router
:
router.push
Gère les transitions côté client. Cette méthode est utile lorsque next/link
ne suffit pas.
router.push(url, as, options)
url
:UrlObject | String
- L'URL vers laquelle naviguer (voir la documentation du module URL de Node.JS pour les propriétés deUrlObject
).as
:UrlObject | String
- Décorateur optionnel pour le chemin qui sera affiché dans la barre d'URL du navigateur. Avant Next.js 9.5.3, cela était utilisé pour les routes dynamiques.options
- Objet optionnel avec les options de configuration suivantes :scroll
- Booléen optionnel, contrôle le défilement vers le haut de la page après la navigation. Par défaut :true
shallow
: Met à jour le chemin de la page actuelle sans relancergetStaticProps
,getServerSideProps
ougetInitialProps
. Par défaut :false
locale
- Chaîne optionnelle, indique la locale de la nouvelle page
Vous n'avez pas besoin d'utiliser
router.push
pour les URL externes. window.location est plus adapté pour ces cas.
Navigation vers pages/about.js
, qui est une route prédéfinie :
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/about')}>
Cliquez ici
</button>
)
}
Navigation vers pages/post/[pid].js
, qui est une route dynamique :
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/post/abc')}>
Cliquez ici
</button>
)
}
Redirection de l'utilisateur vers pages/login.js
, utile pour les pages protégées par authentification :
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Ici, vous récupéreriez et retourneriez l'utilisateur
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
if (!(user || loading)) {
router.push('/login')
}
}, [user, loading])
return <p>Redirection en cours...</p>
}
Réinitialisation de l'état après navigation
Lors de la navigation vers la même page dans Next.js, l'état de la page ne sera pas réinitialisé par défaut, car React ne démonte pas le composant sauf si le composant parent a changé.
import Link from 'next/link'
import { useState } from 'react'
import { useRouter } from 'next/router'
export default function Page(props) {
const router = useRouter()
const [count, setCount] = useState(0)
return (
<div>
<h1>Page : {router.query.slug}</h1>
<p>Compteur : {count}</p>
<button onClick={() => setCount(count + 1)}>Augmenter le compteur</button>
<Link href="/one">one</Link> <Link href="/two">two</Link>
</div>
)
}
Dans l'exemple ci-dessus, naviguer entre /one
et /two
ne réinitialisera pas le compteur. Le useState
est maintenu entre les rendus car le composant React de haut niveau, Page
, est le même.
Si vous ne voulez pas ce comportement, vous avez plusieurs options :
-
Mettre à jour manuellement chaque état avec
useEffect
. Dans l'exemple ci-dessus, cela pourrait ressembler à :useEffect(() => { setCount(0) }, [router.query.slug])
-
Utiliser une
key
React pour indiquer à React de remonter le composant. Pour le faire sur toutes les pages, vous pouvez utiliser une application personnalisée :pages/_app.js import { useRouter } from 'next/router' export default function MyApp({ Component, pageProps }) { const router = useRouter() return <Component key={router.asPath} {...pageProps} /> }
Avec un objet URL
Vous pouvez utiliser un objet URL de la même manière que pour next/link
. Fonctionne pour les paramètres url
et as
:
import { useRouter } from 'next/router'
export default function ReadMore({ post }) {
const router = useRouter()
return (
<button
type="button"
onClick={() => {
router.push({
pathname: '/post/[pid]',
query: { pid: post.id },
})
}}
>
Cliquez ici pour en savoir plus
</button>
)
}
router.replace
Similaire à la prop replace
de next/link
, router.replace
empêche l'ajout d'une nouvelle entrée d'URL dans la pile d'historique.
router.replace(url, as, options)
- L'API de
router.replace
est exactement la même que celle derouter.push
.
Voici un exemple :
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.replace('/home')}>
Cliquez ici
</button>
)
}
router.prefetch
Précharge les pages pour des transitions côté client plus rapides. Cette méthode n'est utile que pour les navigations sans next/link
, car next/link
s'occupe de précharger les pages automatiquement.
Cette fonctionnalité est uniquement disponible en production. Next.js ne précharge pas les pages en développement.
router.prefetch(url, as, options)
url
- L'URL à précharger, incluant les routes explicites (par exemple/dashboard
) et les routes dynamiques (par exemple/product/[id]
)as
- Décorateur optionnel poururl
. Avant Next.js 9.5.3, cela était utilisé pour précharger les routes dynamiques.options
- Objet optionnel avec les champs autorisés suivants :locale
- permet de fournir une locale différente de celle active. Sifalse
,url
doit inclure la locale car la locale active ne sera pas utilisée.
Imaginons que vous ayez une page de connexion, et qu'après la connexion, vous redirigez l'utilisateur vers le tableau de bord. Pour ce cas, nous pouvons précharger le tableau de bord pour une transition plus rapide, comme dans l'exemple suivant :
import { useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'
export default function Login() {
const router = useRouter()
const handleSubmit = useCallback((e) => {
e.preventDefault()
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
/* Données du formulaire */
}),
}).then((res) => {
// Effectue une transition côté client rapide vers la page de tableau de bord déjà préchargée
if (res.ok) router.push('/dashboard')
})
}, [])
useEffect(() => {
// Précharge la page du tableau de bord
router.prefetch('/dashboard')
}, [router])
return (
<form onSubmit={handleSubmit}>
{/* Champs du formulaire */}
<button type="submit">Se connecter</button>
</form>
)
}
router.beforePopState
Dans certains cas (par exemple, si vous utilisez un serveur personnalisé), vous pouvez souhaiter écouter les événements popstate et faire quelque chose avant que le routeur n'agisse.
router.beforePopState(cb)
cb
- La fonction à exécuter lors des événementspopstate
entrants. La fonction reçoit l'état de l'événement sous forme d'objet avec les props suivantes :url
:String
- la route pour le nouvel état. C'est généralement le nom d'unepage
as
:String
- l'URL qui sera affichée dans le navigateuroptions
:Object
- Options supplémentaires envoyées par router.push
Si cb
retourne false
, le routeur Next.js ne gérera pas le popstate
, et vous serez responsable de le gérer dans ce cas. Voir Désactivation du routage par système de fichiers.
Vous pouvez utiliser beforePopState
pour manipuler la requête ou forcer un rafraîchissement SSR, comme dans l'exemple suivant :
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
useEffect(() => {
router.beforePopState(({ url, as, options }) => {
// Je ne veux autoriser que ces deux routes !
if (as !== '/' && as !== '/other') {
// Faire en sorte que le SSR rende les mauvaises routes en 404.
window.location.href = as
return false
}
return true
})
}, [router])
return <p>Bienvenue sur la page</p>
}
router.back
Navigue en arrière dans l'historique. Équivalent à cliquer sur le bouton retour du navigateur. Exécute window.history.back()
.
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.back()}>
Cliquez ici pour revenir en arrière
</button>
)
}
router.reload
Recharge l'URL actuelle. Équivalent à cliquer sur le bouton rafraîchir du navigateur. Exécute window.location.reload()
.
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.reload()}>
Cliquez ici pour recharger
</button>
)
}
router.events
Vous pouvez écouter différents événements se produisant dans le routeur Next.js. Voici une liste des événements supportés :
routeChangeStart(url, { shallow })
- Se déclenche lorsqu'une route commence à changerrouteChangeComplete(url, { shallow })
- Se déclenche lorsqu'une route a complètement changérouteChangeError(err, url, { shallow })
- Se déclenche lorsqu'il y a une erreur lors du changement de route, ou qu'un chargement de route est annuléerr.cancelled
- Indique si la navigation a été annulée
beforeHistoryChange(url, { shallow })
- Se déclenche avant de changer l'historique du navigateurhashChangeStart(url, { shallow })
- Se déclenche lorsque le hash va changer mais pas la pagehashChangeComplete(url, { shallow })
- Se déclenche lorsque le hash a changé mais pas la page
Bon à savoir : Ici,
url
est l'URL affichée dans le navigateur, incluant lebasePath
.
Par exemple, pour écouter l'événement routeChangeStart
du routeur, ouvrez ou créez pages/_app.js
et abonnez-vous à l'événement, comme ceci :
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyApp({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChange = (url, { shallow }) => {
console.log(
`L'application change vers ${url} ${
shallow ? 'avec' : 'sans'
} routage shallow`
)
}
router.events.on('routeChangeStart', handleRouteChange)
// Si le composant est démonté, désabonnez-vous
// de l'événement avec la méthode 'off' :
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [router])
return <Component {...pageProps} />
}
Nous utilisons une Application personnalisée (
pages/_app.js
) pour cet exemple afin de nous abonner à l'événement car elle n'est pas démontée lors des navigations de page, mais vous pouvez vous abonner aux événements du routeur dans n'importe quel composant de votre application.
Les événements du routeur doivent être enregistrés lorsqu'un composant est monté (useEffect ou componentDidMount / componentWillUnmount) ou impérativement lorsqu'un événement se produit.
Si un chargement de route est annulé (par exemple, en cliquant rapidement sur deux liens à la suite), routeChangeError
se déclenchera. Et l'err
passé contiendra une propriété cancelled
définie à true
, comme dans l'exemple suivant :
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyApp({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChangeError = (err, url) => {
if (err.cancelled) {
console.log(`La route vers ${url} a été annulée !`)
}
}
router.events.on('routeChangeError', handleRouteChangeError)
// Si le composant est démonté, désabonnez-vous
// de l'événement avec la méthode 'off' :
return () => {
router.events.off('routeChangeError', handleRouteChangeError)
}
}, [router])
return <Component {...pageProps} />
}
Erreurs potentielles avec ESLint
Certaines méthodes accessibles sur l'objet router
retournent une Promise. Si vous avez activé la règle ESLint no-floating-promises, envisagez de la désactiver soit globalement, soit pour la ligne concernée.
Si votre application nécessite cette règle, vous devriez soit utiliser void
avec la Promise, soit utiliser une fonction async
, await
la Promise, puis passer la fonction en void
. Ceci ne s'applique pas lorsque la méthode est appelée depuis un gestionnaire onClick
.
Les méthodes concernées sont :
router.push
router.replace
router.prefetch
Solutions potentielles
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// Ici, vous récupéreriez et retourneriez l'utilisateur
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
// désactive le linting sur la ligne suivante - C'est la solution la plus propre
// eslint-disable-next-line no-floating-promises
router.push('/login')
// passe en void la Promise retournée par router.push
if (!(user || loading)) {
void router.push('/login')
}
// ou utilise une fonction async, await la Promise, puis passe la fonction en void
async function handleRouteChange() {
if (!(user || loading)) {
await router.push('/login')
}
}
void handleRouteChange()
}, [user, loading])
return <p>Redirection en cours...</p>
}
withRouter
Si useRouter
ne convient pas à votre cas, withRouter
peut également ajouter le même objet router
à n'importe quel composant.
Utilisation
import { withRouter } from 'next/router'
function Page({ router }) {
return <p>{router.pathname}</p>
}
export default withRouter(Page)
TypeScript
Pour utiliser des composants de classe avec withRouter
, le composant doit accepter une prop router :
import React from 'react'
import { withRouter, NextRouter } from 'next/router'
interface WithRouterProps {
router: NextRouter
}
interface MyComponentProps extends WithRouterProps {}
class MyComponent extends React.Component<MyComponentProps> {
render() {
return <p>{this.props.router.pathname}</p>
}
}
export default withRouter(MyComponent)