Añadir búsqueda y paginación
En el capítulo anterior, mejoraste el rendimiento de carga inicial de tu dashboard con streaming. Ahora pasemos a la página /invoices
y aprendamos a añadir búsqueda y paginación.
Código inicial
Dentro de tu archivo /dashboard/invoices/page.tsx
, pega el siguiente código:
Tómate un tiempo para familiarizarte con la página y los componentes con los que trabajarás:
<Search/>
permite a los usuarios buscar facturas específicas.<Pagination/>
permite navegar entre páginas de facturas.<Table/>
muestra las facturas.
La funcionalidad de búsqueda abarcará tanto el cliente como el servidor. Cuando un usuario busque una factura en el cliente, los parámetros de la URL se actualizarán, los datos se obtendrán en el servidor y la tabla se volverá a renderizar en el servidor con los nuevos datos.
¿Por qué usar parámetros de búsqueda en la URL?
Como se mencionó anteriormente, usarás parámetros de búsqueda en la URL para gestionar el estado de búsqueda. Este patrón puede ser nuevo si estás acostumbrado a hacerlo con estado del lado del cliente.
Hay varios beneficios al implementar la búsqueda con parámetros de URL:
- URLs marcables y compartibles: Como los parámetros de búsqueda están en la URL, los usuarios pueden marcar el estado actual de la aplicación, incluyendo sus consultas de búsqueda y filtros, para referencia futura o compartir.
- Renderizado del lado del servidor (SSR): Los parámetros de URL pueden consumirse directamente en el servidor para renderizar el estado inicial, facilitando el manejo del renderizado del servidor.
- Análisis y seguimiento: Tener consultas de búsqueda y filtros directamente en la URL facilita el seguimiento del comportamiento del usuario sin necesidad de lógica adicional del lado del cliente.
Añadiendo la funcionalidad de búsqueda
Estos son los hooks de cliente de Next.js que usarás para implementar la búsqueda:
useSearchParams
- Permite acceder a los parámetros de la URL actual. Por ejemplo, los parámetros de búsqueda para esta URL/dashboard/invoices?page=1&query=pending
se verían así:{page: '1', query: 'pending'}
.usePathname
- Permite leer el pathname de la URL actual. Por ejemplo, para la ruta/dashboard/invoices
,usePathname
devolvería'/dashboard/invoices'
.useRouter
- Permite navegar entre rutas dentro de componentes cliente programáticamente. Hay múltiples métodos que puedes usar.
Aquí tienes un resumen rápido de los pasos de implementación:
- Capturar la entrada del usuario.
- Actualizar la URL con los parámetros de búsqueda.
- Mantener la URL sincronizada con el campo de entrada.
- Actualizar la tabla para reflejar la consulta de búsqueda.
1. Capturar la entrada del usuario
Ve al componente <Search>
(/app/ui/search.tsx
), y notarás:
"use client"
- Este es un Componente Cliente, lo que significa que puedes usar event listeners y hooks.<input>
- Este es el campo de búsqueda.
Crea una nueva función handleSearch
, y añade un listener onChange
al elemento <input>
. onChange
invocará handleSearch
cada vez que cambie el valor del input.
Verifica que funciona correctamente abriendo la consola en las herramientas de desarrollo de tu navegador, luego escribe en el campo de búsqueda. Deberías ver el término de búsqueda registrado en la consola.
¡Genial! Estás capturando la entrada de búsqueda del usuario. Ahora necesitas actualizar la URL con el término de búsqueda.
2. Actualizar la URL con los parámetros de búsqueda
Importa el hook useSearchParams
de next/navigation
y asígnalo a una variable:
Dentro de handleSearch
, crea una nueva instancia de URLSearchParams
usando tu variable searchParams
.
URLSearchParams
es una API web que proporciona métodos útiles para manipular los parámetros de consulta de la URL. En lugar de crear un literal de cadena complejo, puedes usarlo para obtener la cadena de parámetros como ?page=1&query=a
.
A continuación, set
la cadena de parámetros basada en la entrada del usuario. Si la entrada está vacía, querrás eliminarla
:
Ahora que tienes la cadena de consulta. Puedes usar los hooks useRouter
y usePathname
de Next.js para actualizar la URL.
Importa useRouter
y usePathname
de 'next/navigation'
, y usa el método replace
de useRouter()
dentro de handleSearch
:
Aquí tienes un desglose de lo que está pasando:
${pathname}
es la ruta actual, en tu caso,"/dashboard/invoices"
.- A medida que el usuario escribe en la barra de búsqueda,
params.toString()
traduce esta entrada a un formato compatible con URL. replace(${pathname}?${params.toString()})
actualiza la URL con los datos de búsqueda del usuario. Por ejemplo,/dashboard/invoices?query=lee
si el usuario busca "Lee".- La URL se actualiza sin recargar la página, gracias a la navegación del lado del cliente de Next.js (que aprendiste en el capítulo sobre navegar entre páginas.
3. Mantener la URL y la entrada sincronizadas
Para asegurar que el campo de entrada esté sincronizado con la URL y se rellene al compartir, puedes pasar un defaultValue
al input leyendo de searchParams
:
defaultValue
vs.value
/ Controlado vs. No controladoSi usas estado para gestionar el valor de un input, usarías el atributo
value
para hacerlo un componente controlado. Esto significa que React gestionaría el estado del input.Sin embargo, como no estás usando estado, puedes usar
defaultValue
. Esto significa que el input nativo gestionará su propio estado. Esto está bien ya que estás guardando la consulta de búsqueda en la URL en lugar del estado.
4. Actualizar la tabla
Finalmente, necesitas actualizar el componente de tabla para reflejar la consulta de búsqueda.
Navega de vuelta a la página de facturas.
Los componentes de página aceptan una prop llamada searchParams
, así que puedes pasar los parámetros de URL actuales al componente <Table>
.
Si navegas al componente <Table>
, verás que las dos props, query
y currentPage
, se pasan a la función fetchFilteredInvoices()
que devuelve las facturas que coinciden con la consulta.
Con estos cambios implementados, adelante y pruébalo. Si buscas un término, actualizarás la URL, lo que enviará una nueva solicitud al servidor, los datos se obtendrán en el servidor y solo se devolverán las facturas que coincidan con tu consulta.
¿Cuándo usar el hook
useSearchParams()
vs. la propsearchParams
?Puede que hayas notado que usaste dos formas diferentes de extraer parámetros de búsqueda. Usar una u otra depende de si estás trabajando en el cliente o en el servidor.
<Search>
es un Componente Cliente, así que usaste el hookuseSearchParams()
para acceder a los parámetros desde el cliente.<Table>
es un Componente Servidor que obtiene sus propios datos, así que puedes pasar la propsearchParams
desde la página al componente.Como regla general, si quieres leer los parámetros desde el cliente, usa el hook
useSearchParams()
ya que esto evita tener que volver al servidor.
Mejor práctica: Debouncing
¡Felicidades! Has implementado la búsqueda con Next.js. Pero hay algo que puedes hacer para optimizarla.
Dentro de tu función handleSearch
, añade el siguiente console.log
:
Luego escribe "Delba" en tu barra de búsqueda y revisa la consola en las herramientas de desarrollo. ¿Qué está pasando?
¡Estás actualizando la URL con cada pulsación de tecla y, por lo tanto, consultando tu base de datos en cada pulsación! Esto no es un problema ya que nuestra aplicación es pequeña, pero imagina si tu aplicación tuviera miles de usuarios, cada uno enviando una nueva solicitud a tu base de datos con cada tecla.
Debouncing es una práctica de programación que limita la frecuencia con la que una función puede ejecutarse. En nuestro caso, solo quieres consultar la base de datos cuando el usuario haya dejado de escribir.
Cómo funciona el Debouncing:
- Evento de Disparo: Cuando ocurre un evento que debe ser debounced (como una pulsación de tecla en el cuadro de búsqueda), se inicia un temporizador.
- Espera: Si ocurre un nuevo evento antes de que el temporizador expire, el temporizador se reinicia.
- Ejecución: Si el temporizador llega al final de su cuenta regresiva, se ejecuta la función debounced.
Puedes implementar debouncing de varias maneras, incluyendo la creación manual de tu propia función debounce. Para mantener las cosas simples, usaremos una biblioteca llamada use-debounce
.
Instala use-debounce
:
En tu componente <Search>
, importa una función llamada useDebouncedCallback
:
Esta función envolverá el contenido de handleSearch
y solo ejecutará el código después de un tiempo específico una vez que el usuario haya dejado de escribir (300ms).
Ahora escribe nuevamente en tu barra de búsqueda y abre la consola en las herramientas de desarrollo. Deberías ver lo siguiente:
Al usar debouncing, puedes reducir el número de solicitudes enviadas a tu base de datos, ahorrando así recursos.
Añadiendo paginación
Después de introducir la función de búsqueda, notarás que la tabla muestra solo 6 facturas a la vez. Esto se debe a que la función fetchFilteredInvoices()
en data.ts
devuelve un máximo de 6 facturas por página.
Añadir paginación permite a los usuarios navegar por las diferentes páginas para ver todas las facturas. Veamos cómo puedes implementar la paginación usando parámetros de URL, tal como lo hiciste con la búsqueda.
Navega al componente <Pagination/>
y notarás que es un Componente de Cliente. No quieres obtener datos en el cliente ya que esto expondría los secretos de tu base de datos (recuerda, no estás usando una capa de API). En su lugar, puedes obtener los datos en el servidor y pasarlos al componente como una prop.
En /dashboard/invoices/page.tsx
, importa una nueva función llamada fetchInvoicesPages
y pasa el query
de searchParams
como argumento:
fetchInvoicesPages
devuelve el número total de páginas basado en la consulta de búsqueda. Por ejemplo, si hay 12 facturas que coinciden con la consulta de búsqueda y cada página muestra 6 facturas, entonces el número total de páginas sería 2.
A continuación, pasa la prop totalPages
al componente <Pagination/>
:
Navega al componente <Pagination/>
e importa los hooks usePathname
y useSearchParams
. Los usaremos para obtener la página actual y establecer la nueva página. Asegúrate también de descomentar el código en este componente. Tu aplicación se romperá temporalmente ya que aún no has implementado la lógica de <Pagination/>
. ¡Hagámoslo ahora!
A continuación, crea una nueva función dentro del componente <Pagination>
llamada createPageURL
. Similarmente a la búsqueda, usarás URLSearchParams
para establecer el nuevo número de página y pathName
para crear la cadena de URL.
Aquí tienes un desglose de lo que está pasando:
createPageURL
crea una instancia de los parámetros de búsqueda actuales.- Luego, actualiza el parámetro "page" al número de página proporcionado.
- Finalmente, construye la URL completa usando el pathname y los parámetros de búsqueda actualizados.
El resto del componente <Pagination>
se ocupa del estilo y los diferentes estados (primero, último, activo, desactivado, etc.). No entraremos en detalle en este curso, pero siéntete libre de revisar el código para ver dónde se llama a createPageURL
.
Finalmente, cuando el usuario escribe una nueva consulta de búsqueda, quieres restablecer el número de página a 1. Puedes hacer esto actualizando la función handleSearch
en tu componente <Search>
:
Resumen
¡Felicidades! Acabas de implementar búsqueda y paginación usando parámetros de búsqueda de URL y APIs de Next.js.
Para resumir, en este capítulo:
- Has manejado la búsqueda y paginación con parámetros de búsqueda de URL en lugar de estado del cliente.
- Has obtenido datos en el servidor.
- Estás usando el hook de enrutador
useRouter
para transiciones más suaves en el lado del cliente.
Estos patrones son diferentes a lo que puedes estar acostumbrado cuando trabajas con React del lado del cliente, pero esperamos que ahora entiendas mejor los beneficios de usar parámetros de búsqueda de URL y elevar este estado al servidor.