Retour Illustration dans le style de Moebius. Dans un univers futuriste et apocalyptique, 
            un développeur assis par terre dans un hangar tente de régler un bug sur un panneau d'affichage 
            de texte déroulant montrant une URL suivie de paramètres.

Comment implémenter des paramètres de recherche avec React Router v.6 ?

Cet article est un work in progress.

Note pour moi même: gérer les null, conclusion, relecture, test IRL, illustration...

Question

J'ai une page affichant une liste filtrable et paginable.

L'UX de cette page est mauvaise: si je consulte un élement de la liste puis que je retourne sur cette liste, je perd mes filtres et retourne à la page 1.

Voici mon composant :

Comment faire ?

            
              import React, { useState } from 'react';
              
              const UsersList = () => {
                const [page, setPage] = useState(1)
                const [search, setSearch] = useState("")
                const [status, setStatus] = useState([])

                return (
                  <>
                    <h1>Users</h1>

                    <Filters 
                        searchValue={search}
                        onSearch={(newSearch) => setSearch(newSearch)}
                        statusValue={status}
                        onStatusChange={(newValue) => setStatus(newValue)}
                      />

                    <List/>

                    <Pagination
                      page={page}
                      onChange={(newPage) => setPage(newPage)}
                      />
                  </>
                )   
              }
            
          

Réponse

React Router v.6 fournit le hook useSearchParams.

TL;DR

Voilà comment procéder
            
            CODE FINAL
            
          

Explications

L'idée ici est de remplacer les filtres et la pagination utilisants le useState de React par le useSearchParams de React Router. Cela va permettre de gérer l'état (= state) du composant directement dans l'URL.

Ce hook nous retourne un array de 2 valeurs : searchParams qui est les paramètres de la localisation en cours, et setSearchParams, une fonction qui peut être utilisée pour mettre à jour ces mêmes paramètres.


Commençons par la pagination.

  1. On commence par remplacer useState par useSearchParams en lui fournissant une valeur de page par défaut.

  2. Puis on recupère la valeur page venant de l'URL. L'URL renvoyant toujours de string, il faut veiller à convertir la valeur en nombre.

  3. Et l'on met à jour les paramètres d'URL au moment de la pagination.

            
              import React, { useState } from 'react';
              import { useSearchParams } from "react-router-dom";
              
              const UsersList = () => {
                const [page, setPage] = useState(1)
                const [searchParams, setSearchParams] = useSearchParams({ page: 1 });
                ...

                return (
                  <>
                    <h1>Users</h1>

                    ...
                    
                    <Pagination
                      page={page}
                      page={+(searchParams.get("page"))}
                      onChange={(newPage) => setPage(newPage)}
                      onChange={(newPage) => setSearchParams(prev => {
                          prev.set(page: newPage)
                          return prev
                        })
                      }
                      />
                  </>
                )   
              }
            
          

Et voilà ! L'URL sera mise à jour à chaque changement de page et sera persistée dans l'URL pour retrouver la même page en revenant sur cette liste.

Vous n'avez plus qu'à décliner ce code sur les autres filtres :

              
                import React, { useState } from 'react';
                import { useSearchParams } from "react-router-dom";
                
                const UsersList = () => {
                  const [searchParams, setSearchParams] = useSearchParams({ page: 1, search: "", status: [] });
  
                  return (
                    <>
                      <h1>Users</h1>

                      <Filters 
                        searchValue={search}
                        searchValue={searchParams.get("search")}
                        onSearch={(newSearch) => setSearch(newSearch)}
                        onSearch={(newSearch) => (newPage) => setSearchParams(prev => {
                                prev.set(search: newSearch)
                                return prev
                              }, { replace: true })
                            }
                        statusValue={status}
                        statusValue={searchParams.get("status")}
                        onStatusChange={(newStatus) => setStatus(newStatus)}
                        onStatusChange={(newStatus) => setSearchParams(prev => {
                              prev.set(status: newStatus)
                              return prev
                              }, { replace: true }
                            )}
                      />

                      <Pagination
                      page={+(searchParams.get("page"))}
                      onChange={(newPage) => setSearchParams({ page: newPage })}
                      onChange={(newPage) => setSearchParams(prev => {
                          prev.set(page: newPage)
                          return prev
                          }, { replace: true })
                      }
                      />

                    </>
                  )   
                }
              
            
Info

Notez ici le { replace: true }.

Si plusieurs filtres sont appliqués à la suite, le retour en arrière remontera chaque changement appliqué sur l'URL. Cette option permet d'éviter celà.

Ré-usinage

Ce code fonctionne mais il est très redondant donc nous allons le ré-usiner.

  1. On va isoler la récupération des variable en un même endroit, pour une meilleure lecture. Nous pourrions d'ailleurs aussi gérer les "null" de façon plus propre, mais ce n'est pas le sujet de l'article.

  2. Puis on créé une fonction handleFilters() qui prend une clé et une valeur en paramètre, et qui sera utilisée à chaque mise à jour des paramètres.

            
              import React, { useState } from 'react';
              import { useSearchParams } from "react-router-dom";
              
              const UsersList = () => {
                const [searchParams, setSearchParams] = useSearchParams({ page: 1, search: "", status: [] });
                const page = +(searchParams.get("page"))
                const search = searchParams.get("search")
                const status = searchParams.get("status")

                const handleFilters = (key, value) => {
                  setSearchParams(
                    prev => {
                      prev.set(key, value)
                      return prev
                    },
                    { replace: true },
                  )
                  if (key !== 'page') handleFilters('page', 1)
                }

                return (
                  <>
                    <h1>Users</h1>

                    <Filters 
                      searchValue={searchParams.get("search")}
                      searchValue={search}
                      onSearch={(newSearch) => (newPage) => setSearchParams(prev => {
                          prev.set(search: newSearch)
                          return prev
                        }, { replace: true })
                      }
                      onSearch={(newSearch) => handleFilter("search", newSearch)}
                      onStatusChange={(newStatus) => setSearchParams(prev => {
                        prev.set(status: newStatus)
                        return prev
                        }, { replace: true }
                      )}
                      onStatusChange={(newStatus) => handleFilter("status", newStatus)}
                      
                    />

                    <Pagination
                    page={+(searchParams.get("page"))}
                    page={page}
                    onChange={(newPage) => setSearchParams(prev => {
                        prev.set(page: newPage)
                        return prev
                      }, { replace: true })
                    }
                    onChange={(newPage) => handleFilters("page", newPage)}
                    />

                  </>
                )   
              }
            
          
INFO

Si besoin est, pour simuler le comportement du bouton de retour en arrière natif de votre navigateur, vous pouvez invoquer navigate(-1) issu de useNavigate, un autre hook de React Router v.6.

Retour