Ajout de recherche et de pagination
Dans le chapitre précédent, vous avez amélioré les performances de chargement initial de votre tableau de bord avec le streaming. Passons maintenant à la page /invoices
et voyons comment ajouter la recherche et la pagination.
Code de départ
Dans votre fichier /dashboard/invoices/page.tsx
, collez le code suivant :
Prenez le temps de vous familiariser avec la page et les composants sur lesquels vous allez travailler :
<Search/>
permet aux utilisateurs de rechercher des factures spécifiques.<Pagination/>
permet aux utilisateurs de naviguer entre les pages de factures.<Table/>
affiche les factures.
Votre fonctionnalité de recherche s'étendra entre le client et le serveur. Lorsqu'un utilisateur recherche une facture côté client, les paramètres de l'URL seront mis à jour, les données seront récupérées côté serveur, et le tableau sera re-rendu côté serveur avec les nouvelles données.
Pourquoi utiliser les paramètres de recherche d'URL ?
Comme mentionné ci-dessus, vous utiliserez les paramètres de recherche d'URL pour gérer l'état de la recherche. Ce modèle peut être nouveau si vous avez l'habitude de le faire avec un état côté client.
Il y a plusieurs avantages à implémenter la recherche avec des paramètres d'URL :
- URLs pouvant être sauvegardées et partagées : Comme les paramètres de recherche sont dans l'URL, les utilisateurs peuvent sauvegarder l'état actuel de l'application, y compris leurs requêtes de recherche et filtres, pour référence future ou partage.
- Rendu côté serveur (SSR) : Les paramètres d'URL peuvent être directement utilisés côté serveur pour rendre l'état initial, ce qui facilite la gestion du rendu serveur.
- Analyse et suivi : Avoir les requêtes de recherche et les filtres directement dans l'URL facilite le suivi du comportement des utilisateurs sans nécessiter de logique client supplémentaire.
Ajout de la fonctionnalité de recherche
Voici les hooks clients Next.js que vous utiliserez pour implémenter la fonctionnalité de recherche :
useSearchParams
- Permet d'accéder aux paramètres de l'URL actuelle. Par exemple, les paramètres de recherche pour cette URL/dashboard/invoices?page=1&query=pending
ressembleraient à ceci :{page: '1', query: 'pending'}
.usePathname
- Permet de lire le chemin de l'URL actuelle. Par exemple, pour la route/dashboard/invoices
,usePathname
retournerait'/dashboard/invoices'
.useRouter
- Permet de naviguer entre les routes dans les composants clients de manière programmatique. Il existe plusieurs méthodes que vous pouvez utiliser.
Voici un aperçu rapide des étapes d'implémentation :
- Capturer la saisie de l'utilisateur.
- Mettre à jour l'URL avec les paramètres de recherche.
- Maintenir l'URL synchronisée avec le champ de saisie.
- Mettre à jour le tableau pour refléter la requête de recherche.
1. Capturer la saisie de l'utilisateur
Allez dans le composant <Search>
(/app/ui/search.tsx
), et vous remarquerez :
"use client"
- Il s'agit d'un composant client, ce qui signifie que vous pouvez utiliser des écouteurs d'événements et des hooks.<input>
- C'est le champ de recherche.
Créez une nouvelle fonction handleSearch
, et ajoutez un écouteur onChange
à l'élément <input>
. onChange
invoquera handleSearch
chaque fois que la valeur de l'entrée change.
Vérifiez que cela fonctionne correctement en ouvrant la console dans les outils de développement de votre navigateur, puis tapez dans le champ de recherche. Vous devriez voir le terme de recherche enregistré dans la console du navigateur.
Parfait ! Vous capturez la saisie de recherche de l'utilisateur. Maintenant, vous devez mettre à jour l'URL avec le terme de recherche.
2. Mettre à jour l'URL avec les paramètres de recherche
Importez le hook useSearchParams
depuis next/navigation
et assignez-le à une variable :
Dans handleSearch
, créez une nouvelle instance URLSearchParams
en utilisant votre variable searchParams
.
URLSearchParams
est une API Web qui fournit des méthodes utilitaires pour manipuler les paramètres de requête d'URL. Au lieu de créer un littéral de chaîne complexe, vous pouvez l'utiliser pour obtenir la chaîne de paramètres comme ?page=1&query=a
.
Ensuite, set
la chaîne de paramètres en fonction de la saisie de l'utilisateur. Si l'entrée est vide, vous voulez la supprimer
:
Maintenant que vous avez la chaîne de requête. Vous pouvez utiliser les hooks useRouter
et usePathname
de Next.js pour mettre à jour l'URL.
Importez useRouter
et usePathname
depuis 'next/navigation'
, et utilisez la méthode replace
de useRouter()
dans handleSearch
:
Voici une explication de ce qui se passe :
${pathname}
est le chemin actuel, dans votre cas,"/dashboard/invoices"
.- Lorsque l'utilisateur tape dans la barre de recherche,
params.toString()
traduit cette entrée dans un format compatible URL. replace(${pathname}?${params.toString()})
met à jour l'URL avec les données de recherche de l'utilisateur. Par exemple,/dashboard/invoices?query=lee
si l'utilisateur recherche "Lee".- L'URL est mise à jour sans recharger la page, grâce à la navigation côté client de Next.js (que vous avez apprise dans le chapitre sur la navigation entre les pages.
3. Maintenir l'URL et l'entrée synchronisées
Pour vous assurer que le champ de saisie est synchronisé avec l'URL et sera prérempli lors du partage, vous pouvez passer une defaultValue
à l'entrée en lisant depuis searchParams
:
defaultValue
vs.value
/ Contrôlé vs. Non contrôléSi vous utilisez un état pour gérer la valeur d'une entrée, vous utiliseriez l'attribut
value
pour en faire un composant contrôlé. Cela signifie que React gérerait l'état de l'entrée.Cependant, puisque vous n'utilisez pas d'état, vous pouvez utiliser
defaultValue
. Cela signifie que l'entrée native gérera son propre état. C'est acceptable puisque vous enregistrez la requête de recherche dans l'URL plutôt que dans l'état.
4. Mise à jour du tableau
Enfin, vous devez mettre à jour le composant de tableau pour refléter la requête de recherche.
Revenez à la page des factures.
Les composants de page acceptent une prop appelée searchParams
, donc vous pouvez passer les paramètres d'URL actuels au composant <Table>
.
Si vous naviguez vers le composant <Table>
, vous verrez que les deux props, query
et currentPage
, sont passées à la fonction fetchFilteredInvoices()
qui retourne les factures correspondant à la requête.
Avec ces changements en place, allez-y et testez. Si vous recherchez un terme, vous mettrez à jour l'URL, ce qui enverra une nouvelle requête au serveur, les données seront récupérées côté serveur, et seules les factures correspondant à votre requête seront retournées.
Quand utiliser le hook
useSearchParams()
vs. la propsearchParams
?Vous avez peut-être remarqué que vous avez utilisé deux méthodes différentes pour extraire les paramètres de recherche. Le choix dépend de si vous travaillez côté client ou serveur.
<Search>
est un composant client, donc vous avez utilisé le hookuseSearchParams()
pour accéder aux paramètres depuis le client.<Table>
est un composant serveur qui récupère ses propres données, donc vous pouvez passer la propsearchParams
de la page au composant.En règle générale, si vous voulez lire les paramètres depuis le client, utilisez le hook
useSearchParams()
car cela évite de devoir retourner vers le serveur.
Bonnes pratiques : Debouncing
Félicitations ! Vous avez implémenté une fonction de recherche avec Next.js ! Mais il y a quelque chose que vous pouvez faire pour l'optimiser.
Dans votre fonction handleSearch
, ajoutez le console.log
suivant :
Ensuite, tapez "Delba" dans votre barre de recherche et vérifiez la console dans les outils de développement. Que se passe-t-il ?
Vous mettez à jour l'URL à chaque frappe, et donc vous interrogez votre base de données à chaque frappe ! Ce n'est pas un problème pour notre petite application, mais imaginez si votre application avait des milliers d'utilisateurs, chacun envoyant une nouvelle requête à votre base de données à chaque frappe.
Le debouncing (anti-rebond) est une pratique de programmation qui limite la fréquence à laquelle une fonction peut s'exécuter. Dans notre cas, vous ne voulez interroger la base de données que lorsque l'utilisateur a arrêté de taper.
Comment fonctionne le debouncing :
- Événement déclencheur : Lorsqu'un événement devant être debouncé (comme une frappe dans la barre de recherche) se produit, un minuteur démarre.
- Attente : Si un nouvel événement se produit avant l'expiration du minuteur, celui-ci est réinitialisé.
- Exécution : Si le minuteur atteint la fin de son décompte, la fonction debouncée est exécutée.
Vous pouvez implémenter le debouncing de plusieurs manières, y compris en créant manuellement votre propre fonction de debouncing. Pour rester simple, nous allons utiliser une bibliothèque appelée use-debounce
.
Installez use-debounce
:
Dans votre composant <Search>
, importez une fonction appelée useDebouncedCallback
:
Cette fonction va encapsuler le contenu de handleSearch
et n'exécuter le code qu'après un délai spécifique une fois que l'utilisateur a arrêté de taper (300ms).
Maintenant, tapez à nouveau dans votre barre de recherche et ouvrez la console dans les outils de développement. Vous devriez voir ceci :
En utilisant le debouncing, vous pouvez réduire le nombre de requêtes envoyées à votre base de données, économisant ainsi des ressources.
Ajout de la pagination
Après avoir introduit la fonction de recherche, vous remarquerez que le tableau n'affiche que 6 factures à la fois. C'est parce que la fonction fetchFilteredInvoices()
dans data.ts
retourne un maximum de 6 factures par page.
L'ajout de la pagination permet aux utilisateurs de naviguer à travers les différentes pages pour voir toutes les factures. Voyons comment vous pouvez implémenter la pagination en utilisant les paramètres d'URL, comme vous l'avez fait avec la recherche.
Naviguez vers le composant <Pagination/>
et vous remarquerez que c'est un composant client. Vous ne voulez pas récupérer des données côté client car cela exposerait les secrets de votre base de données (rappelez-vous, vous n'utilisez pas de couche API). À la place, vous pouvez récupérer les données côté serveur et les passer au composant en tant que prop.
Dans /dashboard/invoices/page.tsx
, importez une nouvelle fonction appelée fetchInvoicesPages
et passez le query
de searchParams
comme argument :
fetchInvoicesPages
retourne le nombre total de pages en fonction de la requête de recherche. Par exemple, s'il y a 12 factures correspondant à la requête de recherche et que chaque page affiche 6 factures, alors le nombre total de pages serait 2.
Ensuite, passez la prop totalPages
au composant <Pagination/>
:
Naviguez vers le composant <Pagination/>
et importez les hooks usePathname
et useSearchParams
. Nous les utiliserons pour obtenir la page actuelle et définir la nouvelle page. Assurez-vous également de décommenter le code dans ce composant. Votre application va temporairement casser car vous n'avez pas encore implémenté la logique de <Pagination/>
. Faisons cela maintenant !
Ensuite, créez une nouvelle fonction dans le composant <Pagination>
appelée createPageURL
. Comme pour la recherche, vous utiliserez URLSearchParams
pour définir le nouveau numéro de page et pathName
pour créer la chaîne d'URL.
Voici ce qui se passe :
createPageURL
crée une instance des paramètres de recherche actuels.- Ensuite, elle met à jour le paramètre "page" avec le numéro de page fourni.
- Enfin, elle construit l'URL complète en utilisant le chemin et les paramètres de recherche mis à jour.
Le reste du composant <Pagination>
concerne le style et les différents états (première page, dernière page, active, désactivée, etc.). Nous n'entrerons pas dans les détails pour ce cours, mais n'hésitez pas à parcourir le code pour voir où createPageURL
est appelée.
Enfin, lorsque l'utilisateur tape une nouvelle requête de recherche, vous voulez réinitialiser le numéro de page à 1. Vous pouvez faire cela en mettant à jour la fonction handleSearch
dans votre composant <Search>
:
Résumé
Félicitations ! Vous venez d'implémenter la recherche et la pagination en utilisant les paramètres de recherche d'URL et les API de Next.js.
Pour résumer, dans ce chapitre :
- Vous avez géré la recherche et la pagination avec des paramètres de recherche d'URL au lieu d'un état client.
- Vous avez récupéré des données côté serveur.
- Vous avez utilisé le hook de routeur
useRouter
pour des transitions plus fluides côté client.
Ces modèles diffèrent de ce à quoi vous pourriez être habitué lorsque vous travaillez avec React côté client, mais nous espérons que vous comprenez maintenant mieux les avantages d'utiliser les paramètres de recherche d'URL et de remonter cet état côté serveur.