react-magic-search-params 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -1037
- package/dist/react-magic-search-params.cjs.development.js.map +1 -1
- package/dist/react-magic-search-params.cjs.production.min.js.map +1 -1
- package/dist/react-magic-search-params.esm.js +0 -1
- package/package.json +8 -4
- package/src/useMagicSearchParams.ts +0 -1
- package/dist/react-magic-search-params.esm.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,1081 +4,95 @@
|
|
|
4
4
|
[](https://reactjs.org/)
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
[](https://github.com/user/react-magic-search-params/issues)
|
|
7
|
-
|
|
8
|
-
# `react-magic-search-params` 🪄 <img src="https://static-production.npmjs.com/255a118f56f5346b97e56325a1217a16.svg" width="20px" height="20px" title="This package contains built-in TypeScript declarations" alt="TypeScript icon, indicating that this package has built-in type declarations" class="aa30d277 pl3" data-nosnippet="true">
|
|
7
|
+
# `react-magic-search-params` 🪄 <img src="https://static-production.npmjs.com/255a118f56f5346b97e56325a1217a16.svg" width="20px" height="20px" title="This package contains built-in TypeScript declarations" alt="TypeScript icon, indicating that this package has built-in type declarations">
|
|
9
8
|
|
|
10
9
|
<img src="https://github.com/user-attachments/assets/1f437570-6f30-4c10-b27d-b876f5c557bd" alt="Screenshot" width="800px" />
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
To install this library, run:
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npm install react-magic-search-params
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
<details>
|
|
21
|
-
<summary><strong>Ver versión en español 🇪🇸</strong></summary>
|
|
22
|
-
|
|
23
|
-
## Índice 📑
|
|
24
|
-
|
|
25
|
-
1. [Introducción General](#introducción-general)
|
|
26
|
-
1.1 [Propósito del Hook](#propósito-del-hook)
|
|
27
|
-
1.2 [Contexto de Implementación](#contexto-de-implementación)
|
|
28
|
-
2. [Tipos de Parámetros que Acepta](#tipos-de-parámetros-que-acepta)
|
|
29
|
-
2.1 [mandatory (Obligatorios)](#mandatory-obligatorios)
|
|
30
|
-
2.2 [optional (Opcionales)](#optional-opcionales)
|
|
31
|
-
2.3 [defaultParams](#defaultparams)
|
|
32
|
-
2.4 [forceParams](#forceparams)
|
|
33
|
-
2.5 [omitParamsByValues](#omitparamsbyvalues)
|
|
34
|
-
2.6 [arraySerialization](#arraySerialization)
|
|
35
|
-
3. [Recomendación de Uso con Archivo de Constantes](#recomendación-de-uso-con-archivo-de-constantes)
|
|
36
|
-
4. [Funciones Principales](#funciones-principales)
|
|
37
|
-
4.1 [getParams](#getparams)
|
|
38
|
-
4.2 [updateParams](#updateparams)
|
|
39
|
-
4.3 [clearParams](#clearparams)
|
|
40
|
-
|
|
41
|
-
5. [Características Clave y Beneficios](#características-clave-y-beneficios)
|
|
42
|
-
6. [Ejemplo de Uso & Explicaciones](#ejemplo-de-uso--explicaciones)
|
|
43
|
-
7. [Serialización de Arrays en la URL(nuevo)](#serialización-de-arrays-en-la-url)
|
|
44
|
-
8. [Buenas Prácticas y Consideraciones](#buenas-prácticas-y-consideraciones) ✅
|
|
45
|
-
9. [Pruebas Unitarias con Vitest](#Ejecución-de-pruebas)
|
|
46
|
-
10. [Conclusión](#conclusión) 🎯
|
|
11
|
+
Centralize and type your search parameters (query params) handling in React Router v6+ with TypeScript.
|
|
47
12
|
|
|
48
13
|
---
|
|
49
14
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<img src="https://github.com/user-attachments/assets/1f437570-6f30-4c10-b27d-b876f5c557bd" alt="Captura de pantalla" width="800px" />
|
|
53
|
-
|
|
54
|
-
## Propósito del Hook 🎯
|
|
55
|
-
|
|
56
|
-
El **hook `useMagicSearchParams`** habilita un manejo **avanzado** y **centralizado** de parámetros en la URL.
|
|
57
|
-
Permite definir y unificar lógica para filtrar, paginar o realizar cualquier otra operación que dependa de parámetros en la cadena de consulta (ej. `?page=1&page_size=10`).
|
|
58
|
-
|
|
59
|
-
**Antes (sin autocompletado ni tipado)**
|
|
60
|
-
En esta sección se ilustra rápidamente cómo cambiaba el manejo de parámetros antes de usar el hook y cómo se simplifica con `useMagicSearchParams`.
|
|
61
|
-
|
|
62
|
-
<details>
|
|
63
|
-
<summary><strong>Antes (manejo manual de URLs)❌</strong></summary>
|
|
64
|
-
|
|
65
|
-
```jsx
|
|
66
|
-
// filepath: /example/BeforeHook.tsx
|
|
67
|
-
|
|
68
|
-
export const BeforeHookExample = () => {
|
|
69
|
-
const [searchParams, setSearchParams] = useSearchParams()
|
|
70
|
-
|
|
71
|
-
// Extraer valores manualmente (sin tipado ni validación)
|
|
72
|
-
const page = parseInt(searchParams.get('page') || '1', 10)
|
|
73
|
-
const pageSize = parseInt(searchParams.get('page_size') || '10', 10)
|
|
74
|
-
const search = searchParams.get('search') || ''
|
|
75
|
-
|
|
76
|
-
const handleChangePage = (newPage: number) => {
|
|
77
|
-
searchParams.set('page', newPage.toString())
|
|
78
|
-
setSearchParams(searchParams)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<div>
|
|
83
|
-
<p>Página: {page}</p>
|
|
84
|
-
<p>page_size: {pageSize}</p>
|
|
85
|
-
<p>search: {search}</p>
|
|
86
|
-
{/* Botón para cambiar de página */}
|
|
87
|
-
<button onClick={() => handleChangePage(page + 1)}>Siguiente página</button>
|
|
88
|
-
</div>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
</details>
|
|
94
|
-
<details> <summary><strong>Después (con autocompletado y seguridad)✅</strong></summary>
|
|
95
|
-
|
|
96
|
-
```jsx
|
|
97
|
-
// filepath: /example/AfterHook.tsx
|
|
98
|
-
import { useMagicSearchParams } from "react-magic-search-params";
|
|
99
|
-
import { paramsUsers } from "@/constants/DefaultParamsPage";
|
|
100
|
-
|
|
101
|
-
export const AfterHookExample = () => {
|
|
102
|
-
// contexto de Api externa...
|
|
103
|
-
const { searchParams, getParams, updateParams } = useMagicSearchParams({
|
|
104
|
-
...paramsUsers,
|
|
105
|
-
forceParams: { page_size: paramsUsers.mandatory.page_size }, // se limita a 10
|
|
106
|
-
omitParamsByValues: ["all", "default"],
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
const paramsUser = getParams();
|
|
111
|
-
|
|
112
|
-
async function loadUsers() {
|
|
113
|
-
toast.loading("Cargando...", { id: "loading" });
|
|
114
|
-
|
|
115
|
-
console.log({ paramsUser });
|
|
116
|
-
const { success, message } = await getUsersContext(paramsUser);
|
|
117
|
-
if (success) {
|
|
118
|
-
toast.success(message ?? "Usuarios obtenidos", { id: "loading" });
|
|
119
|
-
setLoading(false);
|
|
120
|
-
} else {
|
|
121
|
-
toast.error(message ?? "Error inesperado al obtener los usuarios", {
|
|
122
|
-
id: "loading",
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
loadUsers();
|
|
127
|
-
}, [searchParams]);
|
|
128
|
-
|
|
129
|
-
// getParams devuelve datos convertidos y tipados con autocompletado
|
|
130
|
-
const { page, page_size, search } = getParams({ convert: true });
|
|
131
|
-
|
|
132
|
-
const handleNextPage = () => {
|
|
133
|
-
const nextPage = { page: (page ?? 1) + 1 };
|
|
134
|
-
updateParams({ newParams: nextpage }); // por defecto se mantienen los otros parámetros de consulta
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return (
|
|
138
|
-
<div>
|
|
139
|
-
{/* Nota: normalmente el input será de tipo "no controlado" debido a que normalmente se utilizar una técnica de "debounce" para demorar la actualización */}
|
|
140
|
-
<input
|
|
141
|
-
defaultValue={search}
|
|
142
|
-
placeholder="Buscar por..."
|
|
143
|
-
onChange={handleSearchChange}
|
|
144
|
-
/>
|
|
145
|
-
<p>Página actual: {page}</p>
|
|
146
|
-
<p>Tamaño de página: {page_size}</p>
|
|
147
|
-
<p>Búsqueda: {search}</p>
|
|
148
|
-
<button onClick={handleNextPage}>Siguiente página</button>
|
|
149
|
-
</div>
|
|
150
|
-
);
|
|
151
|
-
};
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
</details>
|
|
155
|
-
|
|
156
|
-
#### Información Adicional 📋
|
|
157
|
-
|
|
158
|
-
1. **_Tipado Estricto_**
|
|
159
|
-
|
|
160
|
-
- Al definir “mandatory” y “optional” desde un archivo de constantes, TypeScript infiere las claves disponibles en la URL.
|
|
161
|
-
|
|
162
|
-
2. **_Control en la URL_**
|
|
163
|
-
|
|
164
|
-
- “forceParams” refuerza valores fijos (evitando sobrecargas innecesarias de la API).
|
|
165
|
-
“omitParamsByValues” limpia parámetros como “all” o “default” que no aportan información real.
|
|
166
|
-
|
|
167
|
-
3. **_Reutilización en Distintas Vistas_**
|
|
168
|
-
|
|
169
|
-
- Cada vista puede tener su propio `mandatory` y `optional`.
|
|
170
|
-
Se evita duplicar lógica de extracción y validación de parámetros.
|
|
171
|
-
|
|
172
|
-
4. **_Orden Uniforme en la URL_**
|
|
173
|
-
|
|
174
|
-
- **sortParameters** garantiza un orden predecible (primero “page”, luego “page_size”, etc.).
|
|
175
|
-
|
|
176
|
-
### Contexto de su Implementación
|
|
177
|
-
|
|
178
|
-
1. **Evita múltiples fuentes de la verdad**: Al separar la lógica en un hook único, no se repite código en cada archivo.
|
|
179
|
-
2. **Asegura un Estándar Común** Todos los parámetros (obligatorios u opcionales) se definen en un mismo lugar
|
|
180
|
-
3. **Controlar la URL**: Evita inyecciones de parámetros no deseados y mantiene un orden consistente (ej. `?page=1&page_size=10` en vez de ?`page_size=10&page=1`).
|
|
181
|
-
|
|
182
|
-
### Tipos de Parámetros Aceptados
|
|
183
|
-
|
|
184
|
-
1. **Mandatory**:(Obligatorios)
|
|
185
|
-
|
|
186
|
-
- Ejemplo típico: Paginación (page, page_size)
|
|
187
|
-
- Siempre deben existir en la URL para que la vista funcione.
|
|
188
|
-
|
|
189
|
-
2. **Optional**:(Opcionales)
|
|
190
|
-
|
|
191
|
-
- Ejemplo: Filtros de búsqueda (search, order).
|
|
192
|
-
- No afectan la ruta si no están presentes.
|
|
193
|
-
|
|
194
|
-
3. **DefaultParams**:(parámetros por defecto)
|
|
195
|
-
|
|
196
|
-
- Se establecen automáticamente al cargar un componente.
|
|
197
|
-
- Útiles para `filtros por defecto` o configuraciones iniciales.
|
|
198
|
-
- A diferencia de los parámetros agregados en enlaces ej: `sistema/lista?page=1&page_size=10`, estos se cargan según el componente (página) que se este visitando, asegurando que la página visitada siempre tenga parámetros por defecto, aunque el usuario los elimine, esto asegura que las llamadas a una **API** que útiliza los párametros de la URL devuelva los datos correctos.
|
|
199
|
-
|
|
200
|
-
4. **ForceParams**:(Parámetros forzados)
|
|
201
|
-
|
|
202
|
-
- Fuerzan valores que no se pueden sobrescribir (ej: page_size=10).
|
|
203
|
-
- Garantizan una máxima seguridad, mientras que mejoran la experiencia del usuario (evitar page_size=1000)
|
|
204
|
-
|
|
205
|
-
5. **OmitParamsByValues**:(Parámetros omitidos por Valores)
|
|
206
|
-
|
|
207
|
-
- Lista de valores que, si se detectan, se omiten de la **URL** (ej. 'all', 'default')
|
|
208
|
-
- Simplifica URLS, omitiendo parámetros que no aportan información real
|
|
209
|
-
- Reserva espacio para otros parámetros de consulta por la limitación de los mismos en la url _Dependiendo del Navegador que se utilize._
|
|
210
|
-
|
|
211
|
-
6. **arraySerialization**:(Serialización de Arrays)
|
|
212
|
-
|
|
213
|
-
- Permite Serializar arrays en la **URL** con 3 distintos métodos (csv, repeat, brackets)
|
|
214
|
-
- Posibilidad de actualizarlos a través de 2 metodos, toggle (agregar, eliminar) y pasando un array de valores ej tags: ['nuevo1', 'nuevo2']
|
|
215
|
-
- Es accesible a través del metodo `getParams` para obtener sus valores de tipo string ej:`tags=uno,dos,tres` o convertido a su tipo original ej: `tags: ['uno', 'dos','tres']`
|
|
216
|
-
|
|
217
|
-
## Recomendación de uso de un Archivo de Constantes📁
|
|
218
|
-
|
|
219
|
-
- Definir los parámetros obligatorios y opcionales en un único archivo (ej. defaultParamsPage.ts)
|
|
220
|
-
- **Beneficios**:
|
|
221
|
-
- **_Mayor consistencia_**: Todo queda centralizado, lo que significa tener una única fuente de la verdad de los párametros de cada página.
|
|
222
|
-
- **_Tipado seguro_**: Garantiza autocompletado y reduce errores de escritures
|
|
223
|
-
|
|
224
|
-
> [!NOTE]
|
|
225
|
-
> De esta forma Typescript Podrá inferir los tipos de los párametros de consulta y sus valores por defecto a manejar.
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
type UserTagOptions = 'tag1' | 'tag2' | 'react' | 'node' | 'typescript' | 'javascript';
|
|
229
|
-
type OrderUserProps = 'role' | 'date';
|
|
230
|
-
// Puedes especificar los valores para ayudar a que typescript te de más tipado
|
|
231
|
-
export const paramsCrimesDashboard = {
|
|
232
|
-
mandatory: {
|
|
233
|
-
days: 7,
|
|
234
|
-
},
|
|
235
|
-
optional: {},
|
|
236
|
-
};
|
|
237
|
-
export const paramsUsers = {
|
|
238
|
-
mandatory: {
|
|
239
|
-
page: 1,
|
|
240
|
-
page_size: 10 as const,
|
|
241
|
-
only_is_active: false,
|
|
242
|
-
},
|
|
243
|
-
optional: {
|
|
244
|
-
order: "", as OrderUserProps
|
|
245
|
-
search: "",
|
|
246
|
-
tags: ['tag1', 'tag2'] as Array<UserTagOptions>
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
```
|
|
250
|
-
> [!TIP]
|
|
251
|
-
> Al **declarar explícitamente** los tipos (en lugar de basarse únicamente en la inferencia de tipos), se permite que TypeScript proporcione una comprobación de tipos más **estricta**. Esto garantiza que solo se permitan los valores definidos para cada parámetro, lo que ayuda a evitar la asignación accidental de valores no válidos.
|
|
252
|
-
|
|
253
|
-
## Funciones Principales ⚙️
|
|
254
|
-
|
|
255
|
-
### 1. getParams
|
|
256
|
-
|
|
257
|
-
Obtiene los parámetros tipados y opcionalmente convertidos desde la URL.
|
|
258
|
-
Útil para recuperar “page”, “order”, “search”, etc., sin lidiar con valores nulos o tipos incorrectos.
|
|
259
|
-
|
|
260
|
-
> [!NOTE]
|
|
261
|
-
> Por defecto el hook `useSearchParams` de **react-router-dom** devuelve los parámetros en `string`, haunque los hayamos definido con otro tipo ej: `number`, esto lo soluciona el metodo `getParams` gracias a que guarda una referencia de su tipo original.
|
|
262
|
-
|
|
263
|
-
<details>
|
|
264
|
-
<summary><strong>Ejemplo de uso</strong></summary>
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
// Obteniendo valores convertidos
|
|
268
|
-
const { page, search } = getParams({ convert: true });
|
|
269
|
-
|
|
270
|
-
// Ejemplo: mostrar parámetros en consola
|
|
271
|
-
console.log("Página actual:", page); // number
|
|
272
|
-
console.log("Búsqueda:", search); // string | undefined
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
</details>
|
|
276
|
-
|
|
277
|
-
### 2. updateParams
|
|
278
|
-
|
|
279
|
-
Modifica de forma controlada los parámetros en la URL, respetando valores obligatorios;
|
|
280
|
-
puedes reiniciar un valor sin perder el resto (ej. setear `page=1` y mantener `search`).
|
|
281
|
-
|
|
282
|
-
<details>
|
|
283
|
-
<summary><strong>Ejemplo de uso</strong></summary>
|
|
284
|
-
|
|
285
|
-
```typescript
|
|
286
|
-
// Cambiar de página y conservar orden actual
|
|
287
|
-
updateParams({
|
|
288
|
-
newParams: { page: 2 },
|
|
289
|
-
keepParams: { order: true },
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// Establecer un nuevo filtro y reiniciar la página
|
|
293
|
-
updateParams({ newParams: { page: 1, search: "John" } });
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
</details>
|
|
297
|
-
|
|
298
|
-
### 3. clearParams
|
|
299
|
-
|
|
300
|
-
Reinicia los parámetros de la URL, manteniendo (o no) los obligatorios.
|
|
301
|
-
Permite “limpiar” los filtros y volver al estado inicial.
|
|
302
|
-
|
|
303
|
-
<details> <summary><strong>Ejemplo de uso</strong></summary>
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
// Limpia todo y conserva obligatorios
|
|
307
|
-
clearParams();
|
|
308
|
-
|
|
309
|
-
// Limpia incluso los obligatorios
|
|
310
|
-
clearParams({ keepMandatoryParams: false });
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
</details>
|
|
314
|
-
|
|
315
|
-
### Ejemplo de Uso & Explicaciones 🖥️💡
|
|
316
|
-
|
|
317
|
-
En el siguiente ejemplo, se combinan:
|
|
318
|
-
|
|
319
|
-
**mandatory**: Necesarios para la paginación.
|
|
320
|
-
**optional**: Parámetros de búsqueda y orden.
|
|
321
|
-
**forceParams**: Parámetros que no deben cambiar (p. ej. “page_size=1”).
|
|
322
|
-
**omitParamsByValues**: Se omiten valores como “all” o “default”.
|
|
323
|
-
|
|
324
|
-
```jsx
|
|
325
|
-
// filepath: /c:/.../FilterUsers.tsx
|
|
326
|
-
import { useMagicSearchParams } from "@/hooks/UseMagicSearchParams";
|
|
327
|
-
import { paramsUsers } from "@constants/DefaultParamsPage";
|
|
328
|
-
|
|
329
|
-
export const FilterUsers = (props) => {
|
|
330
|
-
const { searchParams, updateParams, clearParams, getParams } =
|
|
331
|
-
useMagicSearchParams({
|
|
332
|
-
...paramsUsers,
|
|
333
|
-
defaultParams: paramsUsers.mandatory,
|
|
334
|
-
forceParams: { page_size: 1 },
|
|
335
|
-
omitParamsByValues: ["all", "default"],
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
// Recuperar parámetros convertidos a sus tipos originales
|
|
339
|
-
const { page, search, order } = getParams({ convert: true });
|
|
340
|
-
|
|
341
|
-
// Actualizar: setear página = 1 y cambiar búsqueda
|
|
342
|
-
const handleChangeSearch = (evt) => {
|
|
343
|
-
updateParams({ newParams: { page: 1, search: evt.target.value } });
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
// Limpiar todo y conservar mandatorios por defecto
|
|
347
|
-
const handleReset = () => {
|
|
348
|
-
clearParams();
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
// ...
|
|
352
|
-
};
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
**En este componente:**
|
|
356
|
-
|
|
357
|
-
**_paramsUsers_** define los objetos “mandatory” y “optional”.
|
|
358
|
-
**_forceParams_** evita que “page*size” sea modificado por el usuario.
|
|
359
|
-
\*\*\_omitParamsByValues*** descarta valores que no aporten datos reales (“all”, “default”).
|
|
360
|
-
**_getParams_** devuelve valores tipados (números, booleanos, strings, etc.).
|
|
361
|
-
**_updateParams_** y **_clearParams_\*\* simplifican los flujos de actualización en la URL.
|
|
362
|
-
|
|
363
|
-
## 7 Serialización de Arrays en la URL 🚀
|
|
364
|
-
|
|
365
|
-
El hook `useMagicSearchParams` ahora permite gestionar parámetros de tipo array de forma **avanzada** y **flexible**, enviándolos de forma óptima al backend según el formato requerido. Esto se logra mediante la opción `arraySerialization`, que admite tres técnicas:
|
|
366
|
-
|
|
367
|
-
### Métodos de Serialización 🔄
|
|
368
|
-
|
|
369
|
-
- **csv**:
|
|
370
|
-
Serializa el array en una única cadena separada por comas.
|
|
371
|
-
**Ejemplo:**
|
|
372
|
-
`tags=tag1,tag2,tag3`
|
|
373
|
-
_Ideal cuando el backend espera un solo string._
|
|
374
|
-
|
|
375
|
-
- **repeat**:
|
|
376
|
-
Envía cada elemento del array como un parámetro separado.
|
|
377
|
-
**Ejemplo:**
|
|
378
|
-
`tags=tag1&tags=tag2&tags=tag3`
|
|
379
|
-
_Perfecto para APIs que manejan múltiples entradas con la misma clave._
|
|
380
|
-
|
|
381
|
-
- **brackets**:
|
|
382
|
-
Utiliza la notación con corchetes en la clave para cada elemento.
|
|
383
|
-
**Ejemplo:**
|
|
384
|
-
`tags[]=tag1&tags[]=tag2&tags[]=tag3`
|
|
385
|
-
_Útil para frameworks que esperan este formato (ej. PHP)._
|
|
386
|
-
|
|
387
|
-
> [!TIP]
|
|
388
|
-
> Al extraer los valores de `tags` con `getParams({ convert: true })` obtendrás:
|
|
389
|
-
>
|
|
390
|
-
> - **String** si no se especifica conversión (ej:csv): `"tags=tag1,tag2,tag3"`
|
|
391
|
-
> - **Array** si se convierte: `tags=['tag1', 'tag2', 'tag3']`
|
|
392
|
-
> _Esto mejora la consistencia y tipado en tu aplicación._
|
|
393
|
-
|
|
394
|
-
### Ventajas y Beneficios 🌟
|
|
395
|
-
|
|
396
|
-
- **Flexibilidad de Envío**:
|
|
397
|
-
Elige el método que mejor se adapte a las necesidades del backend.
|
|
398
|
-
✅ _Mayor compatibilidad con distintos sistemas._
|
|
399
|
-
|
|
400
|
-
- **Normalización Automática**:
|
|
401
|
-
Las claves que llegan en formato `tags[]` se normalizan a `tags` para facilitar su manejo.
|
|
402
|
-
✅ _Más fácil iterar y convertir a tipos originales._
|
|
403
|
-
|
|
404
|
-
- **Control Total de la URL**:
|
|
405
|
-
El hook gestiona la reescritura de la URL de forma consistente, reduciendo errores y manteniendo la legibilidad.
|
|
406
|
-
🔒 _Mejora la seguridad y el control de los parámetros._
|
|
407
|
-
|
|
408
|
-
### Ejemplos de Uso en Código 👨💻
|
|
409
|
-
|
|
410
|
-
```jsx
|
|
411
|
-
import { useMagicSearchParams } from "../src/hooks/useMagicSearchParams";
|
|
412
|
-
import { paramsUsers } from "../src/constants/defaulParamsPage";
|
|
413
|
-
|
|
414
|
-
export default function App() {
|
|
415
|
-
const { searchParams, getParams, updateParams, clearParams } = useMagicSearchParams({
|
|
416
|
-
...paramsUsers,
|
|
417
|
-
defaultParams: paramsUsers.mandatory,
|
|
418
|
-
arraySerialization: 'repeat', // Puedes cambiar a 'csv' o 'brackets' según prefieras.
|
|
419
|
-
omitParamsByValues: ["all", "default"],
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// Obtener parámetros convertidos (por ejemplo, tags se obtiene como array)
|
|
423
|
-
const { tags, page } = getParams({ convert: true });
|
|
15
|
+
## 📖 Introduction
|
|
424
16
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
};
|
|
432
|
-
// pasar un array de tags, útil para agregar múltiples filtros a la vez
|
|
433
|
-
const handleTagToggleArray = (newTags: string[]) => {
|
|
434
|
-
// el hook se encarga de que no existán valores repetidos en el array haciendo
|
|
435
|
-
// merge con los anteriores
|
|
436
|
-
updateParams({ newParams: { tags: [...tags, ..newTags] } });
|
|
437
|
-
};
|
|
438
|
-
return (
|
|
439
|
-
<div>
|
|
440
|
-
<div>
|
|
441
|
-
<h3 className='text-lg font-semibold mb-3'>Selecciona Tags:</h3>
|
|
442
|
-
{availableTags.map(tag => {
|
|
443
|
-
const isActive = Array.isArray(tags) && tags.includes(tag)
|
|
444
|
-
return (
|
|
445
|
-
<button
|
|
446
|
-
key={tag}
|
|
447
|
-
onClick={() => handleTagToggle(tag)}
|
|
448
|
-
className={`px-4 py-2 rounded-md border ${
|
|
449
|
-
isActive ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-700'
|
|
450
|
-
}`}
|
|
451
|
-
>
|
|
452
|
-
{tag}
|
|
453
|
-
</button>
|
|
454
|
-
)
|
|
455
|
-
})}
|
|
456
|
-
</div>
|
|
457
|
-
<p>Tags actuales: {JSON.stringify(tags)}</p>
|
|
458
|
-
{/* Resto del componente */}
|
|
459
|
-
</div>
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
En este ejemplo, al utilizar la serialización **repeat**, la `URL` resultante se verá así:
|
|
465
|
-
|
|
466
|
-
- **Modo (repeat)**: `?page=1&page_size=10&only_is_active=false&tags=tag1&tags=tag2&tags=tag3`
|
|
467
|
-
- **Modo (csv)**: `?page=1&page_size=10&only_is_active=false&tags=tag1,tag2,tag3`
|
|
468
|
-
- **Modo (brackets)**: `?page=1&page_size=10&only_is_active=false&tags[]=tag1&tags[]=tag2&tags[]=tag3`
|
|
469
|
-
|
|
470
|
-
### Esta nueva funcionalidad permite:
|
|
471
|
-
|
|
472
|
-
- Enviar arrays de forma que se ajusten a las expectativas del backend.
|
|
473
|
-
- Gestionar de forma centralizada la conversión y serialización, reduciendo la complejidad en componentes individuales.
|
|
474
|
-
- Mantener la URL limpia y consistente, independientemente del método de serialización elegido.
|
|
475
|
-
- **Dar control total al desarrollador** sobre cómo transformar o enviar los parámetros, permitiendo operaciones personalizadas en función del backend.
|
|
476
|
-
|
|
477
|
-
### ¿Por Qué Esta Funcionalidad Es Clave? 🎯
|
|
478
|
-
|
|
479
|
-
- **Enviar arrays al backend de forma adaptable:**
|
|
480
|
-
Se ajusta a diversos formatos que los servidores pueden esperar.
|
|
481
|
-
- **Reducción de complejidad en componentes:**
|
|
482
|
-
Centraliza la lógica de serialización, evitando redundancia en el código.
|
|
483
|
-
- **Mejor experiencia para el usuario:**
|
|
484
|
-
Una URL limpia y consistente facilita la depuración y mejora la usabilidad.
|
|
485
|
-
|
|
486
|
-
## 8 Buenas Prácticas y Consideraciones ✅
|
|
487
|
-
|
|
488
|
-
1. **Validar parámetros sensibles en backend**: Aunque el hook protege en frontend, el servidor debe imponer límites.
|
|
489
|
-
2. **Mantener los tipos actualizados**: A medida que cambian los requisitos, actualizar “mandatory” y “optional” para evitar descalces.
|
|
490
|
-
3. **Archivo de constantes por vista**: Permite organizar mejor cada pantalla o sección, manteniendo claridad y orden.
|
|
17
|
+
`useMagicSearchParams` allows you to:
|
|
18
|
+
- Define **required** and **optional** parameters in one place.
|
|
19
|
+
- Get typed values (`number`, `string`, `boolean`, `array`).
|
|
20
|
+
- Update or clear the URL without repeating logic.
|
|
21
|
+
- Serialize arrays in `csv`, `repeat` or `brackets` format.
|
|
22
|
+
- Force values (`forceParams`) or omit neutral values (`omitParamsByValues`).
|
|
491
23
|
|
|
492
24
|
---
|
|
493
25
|
|
|
494
|
-
##
|
|
495
|
-
|
|
496
|
-
Este proyecto cuenta con pruebas automatizadas para asegurar su robustez y fiabilidad.
|
|
497
|
-
|
|
498
|
-
### Ejecuta tus pruebas con `Vitest` 🧪
|
|
499
|
-
|
|
500
|
-
Para validar el funcionamiento de este hook (y de los demás), puedes dirigirte al directorio de tests y ejecutar el siguiente comando en la terminal:
|
|
26
|
+
## 🚀 Installation
|
|
501
27
|
|
|
502
28
|
```bash
|
|
503
|
-
npm
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
> [!WARNING]
|
|
507
|
-
> Nota: Asegúrate de tener Vitest configurado en tu proyecto para que estas pruebas se ejecuten correctamente, puedes ver la versión con `npm list`
|
|
508
|
-
|
|
509
|
-
## 10 Conclusión 🎉
|
|
510
|
-
|
|
511
|
-
El hook `useMagicSearchParams` aporta:
|
|
512
|
-
|
|
513
|
-
- **Legibilidad y Mantenibilidad** al centralizar la lógica.
|
|
514
|
-
- **Robustez** en la gestión de parámetros, limitando valores inseguros y permitiendo un flujo coherente.
|
|
515
|
-
|
|
516
|
-
Se recomienda ajustarlo o expandirlo según las necesidades de cada proyecto, añadiendo, por ejemplo, validaciones avanzadas o conversiones adicionales de tipos.
|
|
517
|
-
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
> [!TIP]
|
|
521
|
-
> Puede encontrar ejemplos prácticos de cómo implementar este Hook en ***proyectos grandes**, en el [archivo de ejemplo](./example).
|
|
522
|
-
|
|
523
|
-
</details>
|
|
524
|
-
|
|
525
|
-
## Table of Contents 📑
|
|
526
|
-
|
|
527
|
-
1. [General Introduction](#general-introduction)
|
|
528
|
-
1.1 [Hook Purpose](#hook-purpose)
|
|
529
|
-
1.2 [Implementation Context](#implementation-context)
|
|
530
|
-
2. [Accepted Parameter Types](#accepted-parameter-types)
|
|
531
|
-
2.1 [mandatory (Required)](#mandatory-required)
|
|
532
|
-
2.2 [optional (Optional)](#optional-optional)
|
|
533
|
-
2.3 [defaultParams](#defaultparams)
|
|
534
|
-
2.4 [forceParams](#forceparams)
|
|
535
|
-
2.5 [omitParamsByValues](#omitparamsbyvalues)
|
|
536
|
-
2.6 [arraySerialization](#arrayserialization)
|
|
537
|
-
3. [Usage Recommendation with a Constants File](#usage-recommendation-with-a-constants-file)
|
|
538
|
-
4. [Main Functions](#main-functions)
|
|
539
|
-
4.1 [getParams](#getparams)
|
|
540
|
-
4.2 [updateParams](#updateparams)
|
|
541
|
-
4.3 [clearParams](#clearparams)
|
|
542
|
-
|
|
543
|
-
5. [Key Features and Benefits](#key-features-and-benefits)
|
|
544
|
-
6. [Usage Example & Explanations](#usage-example--explanations)
|
|
545
|
-
7. [Array Serialization in the URL (new)](#array-serialization-in-the-url-new)
|
|
546
|
-
8. [Best Practices and Considerations](#best-practices-and-considerations-)
|
|
547
|
-
9. [Unit Tests with Vitest](#unit-tests-with-vitest)
|
|
548
|
-
10. [Conclusion](#conclusion-)
|
|
549
|
-
|
|
550
|
-
---
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
# General Introduction
|
|
554
|
-
|
|
555
|
-
## Hook Purpose 🎯
|
|
556
|
-
|
|
557
|
-
The **`useMagicSearchParams` hook** enables **advanced** and **centralized** management of URL parameters.
|
|
558
|
-
It allows you to define and unify logic to filter, paginate, or perform any other operation that depends on query parameters (e.g. `?page=1&page_size=10`).
|
|
559
|
-
|
|
560
|
-
**Before (no autocomplete or typing)**
|
|
561
|
-
This section quickly illustrates how parameter handling changed before using the hook and how it simplifies with `useMagicSearchParams`.
|
|
562
|
-
|
|
563
|
-
<details>
|
|
564
|
-
<summary><strong>Before (manual URL handling) ❌</strong></summary>
|
|
565
|
-
|
|
566
|
-
```jsx
|
|
567
|
-
// filepath: /example/BeforeHook.tsx
|
|
568
|
-
|
|
569
|
-
export const BeforeHookExample = () => {
|
|
570
|
-
const [searchParams, setSearchParams] = useSearchParams()
|
|
571
|
-
|
|
572
|
-
// Manually extract values (no typing or validation)
|
|
573
|
-
const page = parseInt(searchParams.get('page') || '1', 10)
|
|
574
|
-
const pageSize = parseInt(searchParams.get('page_size') || '10', 10)
|
|
575
|
-
const search = searchParams.get('search') || ''
|
|
576
|
-
|
|
577
|
-
const handleChangePage = (newPage: number) => {
|
|
578
|
-
searchParams.set('page', newPage.toString())
|
|
579
|
-
setSearchParams(searchParams)
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
return (
|
|
583
|
-
<div>
|
|
584
|
-
<p>Page: {page}</p>
|
|
585
|
-
<p>page_size: {pageSize}</p>
|
|
586
|
-
<p>search: {search}</p>
|
|
587
|
-
{/* Button to move to the next page */}
|
|
588
|
-
<button onClick={() => handleChangePage(page + 1)}>Next page</button>
|
|
589
|
-
</div>
|
|
590
|
-
)
|
|
591
|
-
}
|
|
29
|
+
npm install react-magic-search-params
|
|
592
30
|
```
|
|
593
31
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
</details> <details> <summary><strong>After (with autocomplete and safety) ✅</strong></summary>
|
|
32
|
+
## Basic Usage
|
|
597
33
|
|
|
598
34
|
```jsx
|
|
599
|
-
|
|
600
|
-
import {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
useEffect(() => {
|
|
612
|
-
const paramsUser = getParams();
|
|
613
|
-
|
|
614
|
-
async function loadUsers() {
|
|
615
|
-
toast.loading("Loading...", { id: "loading" });
|
|
616
|
-
|
|
617
|
-
console.log({ paramsUser });
|
|
618
|
-
const { success, message } = await getUsersContext(paramsUser);
|
|
619
|
-
if (success) {
|
|
620
|
-
toast.success(message ?? "Users retrieved", { id: "loading" });
|
|
621
|
-
setLoading(false);
|
|
622
|
-
} else {
|
|
623
|
-
toast.error(message ?? "Unexpected error retrieving the users", {
|
|
624
|
-
id: "loading",
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
loadUsers();
|
|
629
|
-
}, [searchParams]);
|
|
630
|
-
|
|
631
|
-
// getParams returns typed and converted data with autocomplete
|
|
632
|
-
const { page, page_size, search } = getParams({ convert: true });
|
|
35
|
+
import { useMagicSearchParams } from 'react-magic-search-params';
|
|
36
|
+
import { paramsUsers } from './constants';
|
|
37
|
+
|
|
38
|
+
const { getParams, updateParams, clearParams } = useMagicSearchParams({
|
|
39
|
+
...paramsUsers,
|
|
40
|
+
defaultParams: paramsUsers.mandatory,
|
|
41
|
+
forceParams: { page_size: 10 },
|
|
42
|
+
omitParamsByValues: ['all','default'],
|
|
43
|
+
arraySerialization: 'csv',
|
|
44
|
+
});
|
|
633
45
|
|
|
634
|
-
|
|
46
|
+
// Get typed values:
|
|
47
|
+
const { page, search, tags } = getParams({ convert: true });
|
|
635
48
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
updateParams({ newParams: nextPage }); // by default, the rest of the query parameters are preserved
|
|
639
|
-
};
|
|
49
|
+
// Change page:
|
|
50
|
+
updateParams({ newParams: { page: (page ?? 1) + 1 } });
|
|
640
51
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
{/* Note: typically this input will be “uncontrolled,” as you often use a “debounce” approach to delay updates */}
|
|
644
|
-
<input
|
|
645
|
-
defaultValue={search}
|
|
646
|
-
placeholder="Search by..."
|
|
647
|
-
onChange={handleSearchChange}
|
|
648
|
-
/>
|
|
649
|
-
<p>Current page: {page}</p>
|
|
650
|
-
<p>Page size: {page_size}</p>
|
|
651
|
-
<p>Search: {search}</p>
|
|
652
|
-
<button onClick={handleNextPage}>Next page</button>
|
|
653
|
-
</div>
|
|
654
|
-
);
|
|
655
|
-
};
|
|
52
|
+
// Clear filters:
|
|
53
|
+
clearParams();
|
|
656
54
|
```
|
|
657
55
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
> [!NOTE]
|
|
661
|
-
> Since this hook utilizes react-router-dom, make sure your application is wrapped in `<BrowserRouter>` or `<RouterProvider>` (the modern version in React Router v6.4+) so that routing works correctly.
|
|
662
|
-
|
|
663
|
-
#### Additional Information 📋
|
|
664
|
-
|
|
665
|
-
1. **Strict Typing_**
|
|
666
|
-
|
|
667
|
-
- By defining “mandatory” and “optional” from a constants file, TypeScript infers the available keys in the U
|
|
668
|
-
|
|
669
|
-
2. **_Control en la URL_**
|
|
56
|
+
### 📁 Recommended Constants File
|
|
670
57
|
|
|
671
|
-
|
|
672
|
-
“omitParamsByValues” removes “all” or “default” parameters that don’t add real information..
|
|
673
|
-
|
|
674
|
-
3. **_Reuse in Different Views_**
|
|
675
|
-
|
|
676
|
-
- Each view can have its own mandatory and optional. This prevents code duplication for extracting and validating parameters.
|
|
677
|
-
|
|
678
|
-
4. **_Uniform URL Order_**
|
|
679
|
-
|
|
680
|
-
- ensures a predictable order (first “page”, then “page_size”, etc.)
|
|
681
|
-
|
|
682
|
-
### Implementation Context
|
|
683
|
-
|
|
684
|
-
1. **Avoid multiple sources of truth**: By centralizing logic in a single hook, code does not repeat in every file.
|
|
685
|
-
2. **Guarantee a Common Standard** All parameters (required or optional) are defined in a single place and reused in multiple places.
|
|
686
|
-
3. **Control the URL**: Prevents unwanted parameter injections and maintains a consistent order(ej. `?page=1&page_size=10` en instead ?`page_size=1000&page=1`).
|
|
687
|
-
|
|
688
|
-
### Accepted Parameter Typess
|
|
689
|
-
|
|
690
|
-
1. **Mandatory**:(Required)
|
|
691
|
-
|
|
692
|
-
- Typical example: Pagination (page, page_size)
|
|
693
|
-
- They must always exist in the URL for the view to work.
|
|
694
|
-
|
|
695
|
-
2. **Optional**:
|
|
696
|
-
|
|
697
|
-
- Example: Search filters (search, order).
|
|
698
|
-
- They do not affect the **route** if they are not present.
|
|
699
|
-
|
|
700
|
-
3. **DefaultParams**:
|
|
701
|
-
|
|
702
|
-
- Automatically set when loading a component.
|
|
703
|
-
- Useful for `“default filters”` or initial settings.
|
|
704
|
-
- Unlike parameters added in links, e.g. `sistema/lista?page=1&page_size=10`, , these load depending on the component (page) being visited, ensuring that the visited page always has default parameters, even if the user removes them. This ensures that **API** calls depending on URL parameters return correct data.
|
|
705
|
-
|
|
706
|
-
4. **ForceParams**:
|
|
707
|
-
|
|
708
|
-
- Enforce values that cannot be overwritten (e.g. page_size=10).
|
|
709
|
-
- Provides maximum safety while enhancing user experience (avoid page_size=1000).
|
|
710
|
-
|
|
711
|
-
5. **OmitParamsByValues**:(Parameters Omitted by values)
|
|
712
|
-
|
|
713
|
-
- A list of values that, if detected, are omitted from the **URL** (e.g. ‘all’, ‘default’).
|
|
714
|
-
- Simplifies URLs by removing parameters that do not provide real information.
|
|
715
|
-
- Reserves space for other query parameters given potential URL limitations (depending on the browser in use).
|
|
716
|
-
|
|
717
|
-
6. **arraySerialization**:
|
|
718
|
-
|
|
719
|
-
- Allows arrays to be serialized in the **URL** through three different methods (csv, repeat, brackets).
|
|
720
|
-
- Enables updating them via two methods: toggle (add or remove) and passing an array of values (e.g. tags: [‘new1’, ‘new2’])
|
|
721
|
-
- Accessible through the **getParams** method to obtain string-type values (e.g. tags=one,two,three) or converted to their original type (e.g. `tags: [‘one’, ‘two’, ‘three’]`).
|
|
722
|
-
|
|
723
|
-
## 3 Usage Recommendation with a Constants File 📁
|
|
724
|
-
|
|
725
|
-
- Define the required and optional parameters in a single file (e.g. defaultParamsPage.ts).
|
|
726
|
-
- optionally add typing (normally you'll let **typescript** infer)
|
|
727
|
-
- **Beneficios**:
|
|
728
|
-
- **_Greater consistency_**: Everything is centralized, meaning a single source of truth for the parameters of each page.
|
|
729
|
-
- **_Safe typing_**: Guarantees autocomplete and reduces typos.
|
|
730
|
-
|
|
731
|
-
> [!NOTE]
|
|
732
|
-
> This way TypeScript can infer the types of the query parameters and their default values to manag
|
|
733
|
-
|
|
734
|
-
```typescript
|
|
58
|
+
```ts
|
|
735
59
|
type UserTagOptions = 'tag1' | 'tag2' | 'react' | 'node' | 'typescript' | 'javascript';
|
|
736
60
|
type OrderUserProps = 'role' | 'date';
|
|
737
|
-
|
|
738
|
-
export const paramsCrimesDashboard = {
|
|
739
|
-
mandatory: {
|
|
740
|
-
days: 7,
|
|
741
|
-
},
|
|
742
|
-
optional: {},
|
|
743
|
-
};
|
|
61
|
+
|
|
744
62
|
export const paramsUsers = {
|
|
745
|
-
mandatory: {
|
|
63
|
+
mandatory: {
|
|
746
64
|
page: 1,
|
|
747
65
|
page_size: 10 as const,
|
|
748
66
|
only_is_active: false,
|
|
749
67
|
},
|
|
750
|
-
optional:
|
|
751
|
-
order:
|
|
752
|
-
search:
|
|
753
|
-
tags: ['tag1',
|
|
68
|
+
optional: {
|
|
69
|
+
order: '' as OrderUserProps,
|
|
70
|
+
search: '',
|
|
71
|
+
tags: ['tag1','tag2'] as Array<UserTagOptions>
|
|
754
72
|
},
|
|
755
73
|
};
|
|
756
74
|
```
|
|
757
|
-
> [!TIP]
|
|
758
|
-
> By **explicitly declaring** the types (instead of relying solely on type inference), you enable TypeScript to provide **stricter type checking**. This ensures that only the defined values are allowed for each parameter, helping to avoid accidental assignment of invalid values.
|
|
759
|
-
|
|
760
|
-
## 4 Main Functions ⚙️
|
|
761
|
-
|
|
762
|
-
### 4.1 getParams
|
|
763
|
-
|
|
764
|
-
Retrieves typed parameters from the URL and optionally converts them.
|
|
765
|
-
Useful to pull `“page”, “order”, “search”`, etc. without dealing with null values or incorrect types.
|
|
766
|
-
|
|
767
|
-
> [!NOTE]
|
|
768
|
-
> By default, the **react-router-dom** `useSearchParams` hook returns parameters as `string`, even if defined with another type (e.g. `number`). The `getParams` method solves this by keeping a reference to their original type.
|
|
769
|
-
|
|
770
|
-
<details>
|
|
771
|
-
<summary>Example of use</summary>
|
|
772
|
-
|
|
773
|
-
```typescript
|
|
774
|
-
// Retrieving converted values
|
|
775
|
-
const { page, search } = getParams({ convert: true });
|
|
776
|
-
const tags = getParam("tags")
|
|
777
|
-
// tags = [tag1, tag2]
|
|
778
|
-
|
|
779
|
-
// Example: displaying parameters in console
|
|
780
|
-
console.log("Current page:", page); // number
|
|
781
|
-
console.log("Search:", search); // string | undefined
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
</details>
|
|
785
75
|
|
|
786
|
-
###
|
|
76
|
+
### 🌟 Key Functions
|
|
787
77
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
```typescript
|
|
794
|
-
// Change page and keep the current order
|
|
795
|
-
updateParams({
|
|
796
|
-
newParams: { page: 2 },
|
|
797
|
-
keepParams: { order: true },
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
// Set a new filter and reset the page
|
|
801
|
-
updateParams({ newParams: { page: 1, search: "John" } });
|
|
802
|
-
```
|
|
803
|
-
|
|
804
|
-
</details>
|
|
805
|
-
|
|
806
|
-
### 4.3 clearParams
|
|
807
|
-
|
|
808
|
-
Resets the URL parameters, optionally keeping mandatory ones.
|
|
809
|
-
Allows you to “clear” the filters and return to the initial state.
|
|
810
|
-
<details> <summary>Example of use</summary>
|
|
811
|
-
|
|
812
|
-
```typescript
|
|
813
|
-
// Clear everything and keep mandatory parameters
|
|
814
|
-
clearParams();
|
|
815
|
-
|
|
816
|
-
// Clear everything, including mandatory parameters
|
|
817
|
-
clearParams({ keepMandatoryParams: false });
|
|
818
|
-
```
|
|
819
|
-
</details>
|
|
820
|
-
|
|
821
|
-
### 4.4 onChange
|
|
822
|
-
The `onChange` function allows you to subscribe to changes in specific URL parameters. Each time the parameter changes, the associated callbacks will be executed. This is useful when you need to trigger updates or actions (such as API calls, validations, etc.) after a particular value changes.
|
|
823
|
-
|
|
824
|
-
> [!NOTE]
|
|
825
|
-
> Although you will usually want to call an API or trigger other events as soon as changes are detected in any of the URL parameters (by adding `searchParams` to the dependency array of `useEffect`), there are occasions where you may prefer more granular control and only react to specific parameters.
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
**Basic use**
|
|
829
|
-
```jsx
|
|
830
|
-
useEffect(() => {
|
|
831
|
-
function fetchData() {
|
|
832
|
-
|
|
833
|
-
// can be an call to API or any other operation
|
|
834
|
-
return new Promise((resolve) => {
|
|
835
|
-
setTimeout(() => resolve('Information recibed'), 1500)
|
|
836
|
-
})
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
function showData(data: string) {
|
|
840
|
-
alert(`Data recibed: ${data}`)
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
function notifyChange() {
|
|
844
|
-
console.log('Change detected in the parameter search')
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
onChange('search', [
|
|
848
|
-
async () => {
|
|
849
|
-
const data = await fetchData()
|
|
850
|
-
showData(data)
|
|
851
|
-
},
|
|
852
|
-
notifyChange
|
|
853
|
-
])
|
|
854
|
-
}, [])
|
|
855
|
-
// otr
|
|
856
|
-
```
|
|
857
|
-
|
|
858
|
-
### Usage Example & Explanations 🖥️💡
|
|
859
|
-
|
|
860
|
-
In the following example, we combine:
|
|
861
|
-
|
|
862
|
-
**mandatory**: Needed for pagination.
|
|
863
|
-
**optional**: Search and order parameters.
|
|
864
|
-
**forceParams**: Parameters that must not change
|
|
865
|
-
**omitParamsByValues**: Omits `all` or `default` values.
|
|
866
|
-
|
|
867
|
-
```jsx
|
|
868
|
-
// filepath: /c:/.../FilterUsers.tsx
|
|
869
|
-
import { useMagicSearchParams } from "@/hooks/UseMagicSearchParams";
|
|
870
|
-
import { paramsUsers } from "@constants/DefaultParamsPage";
|
|
871
|
-
|
|
872
|
-
export const FilterUsers = (props) => {
|
|
873
|
-
const { searchParams, updateParams, clearParams, getParams } =
|
|
874
|
-
useMagicSearchParams({
|
|
875
|
-
...paramsUsers,
|
|
876
|
-
defaultParams: paramsUsers.mandatory,
|
|
877
|
-
forceParams: { page_size: 1 },
|
|
878
|
-
omitParamsByValues: ["all", "default"],
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
// Retrieve parameters converted to their original types
|
|
882
|
-
const { page, search, order } = getParams({ convert: true });
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
// Update: set page = 1 and change search
|
|
886
|
-
const handleChangeSearch = (evt) => {
|
|
887
|
-
updateParams({ newParams: { page: 1, search: evt.target.value } });
|
|
888
|
-
};
|
|
889
|
-
|
|
890
|
-
// Clear everything and keep mandatory parameters by default
|
|
891
|
-
const handleReset = () => {
|
|
892
|
-
clearParams();
|
|
893
|
-
};
|
|
894
|
-
const handleChangeOrder = (value) => {
|
|
895
|
-
// if value is equal to 'all' it will be ommited
|
|
896
|
-
updateParams({ newParams: { order: value }, keepParams: { search: false }})
|
|
897
|
-
}
|
|
898
|
-
// ...
|
|
899
|
-
return (
|
|
900
|
-
<>
|
|
901
|
-
{/** rest.. */}
|
|
902
|
-
<select name="order" onChange={e => handleChangeOrder(e)}>
|
|
903
|
-
<option value="all">Select tag</option>
|
|
904
|
-
|
|
905
|
-
{/** map of tags..*/}
|
|
906
|
-
</select>
|
|
907
|
-
|
|
908
|
-
</>
|
|
909
|
-
)
|
|
910
|
-
};
|
|
911
|
-
```
|
|
912
|
-
|
|
913
|
-
**En este componente:**
|
|
914
|
-
|
|
915
|
-
**_paramsUsers_**: defines the `mandatory` and `optional` objects.
|
|
916
|
-
**_forceParams_**: prevents “page*size” from being changed by the user.
|
|
917
|
-
**_omitParamsByValues**: discards values that do not provide real data (“all”, “default”)
|
|
918
|
-
**_getParams_**: returns typed values (numbers, booleans, strings, etc.).
|
|
919
|
-
**updateParams** and **clearParams** simplify URL update flows.
|
|
920
|
-
|
|
921
|
-
## 7 Serialización de Arrays en la URL 🚀
|
|
922
|
-
|
|
923
|
-
The **useMagicSearchParams** hook now allows for advanced and flexible handling of a`array-type parameters`, sending them to the `backend` in the format required. This is done via the **arraySerialization** option, which supports three techniques:
|
|
924
|
-
|
|
925
|
-
### Métodos de Serialización 🔄
|
|
926
|
-
|
|
927
|
-
- **csv**:
|
|
928
|
-
Serializes the array into a single comma-separated string.
|
|
929
|
-
**Ejemplo:**
|
|
930
|
-
`tags=tag1,tag2,tag3`
|
|
931
|
-
Ideal when the backend expects a single string.
|
|
932
|
-
|
|
933
|
-
- **repeat**:
|
|
934
|
-
Sends each array element as a separate parameter.
|
|
935
|
-
**Ejemplo:**
|
|
936
|
-
`tags=tag1&tags=tag2&tags=tag3`
|
|
937
|
-
_Perfect for APIs that handle multiple entries under the same key._
|
|
938
|
-
|
|
939
|
-
- **brackets**:
|
|
940
|
-
Uses bracket notation in the key for each element.
|
|
941
|
-
**Ejemplo:**
|
|
942
|
-
`tags[]=tag1&tags[]=tag2&tags[]=tag3`
|
|
943
|
-
_Useful for frameworks expecting this format (e.g. PHP)._
|
|
944
|
-
|
|
945
|
-
> [!TIP]
|
|
946
|
-
> When extracting `tags` with `getParams({ convert: true })` you get:
|
|
947
|
-
>
|
|
948
|
-
> - **Am String** if not conversion is specified (e.g, csv): `"tags=tag1,tag2,tag3"`
|
|
949
|
-
> - **An Array** if conversion is used: `tags=['tag1', 'tag2', 'tag3']`
|
|
950
|
-
> _This improves consistency and typing in your application._
|
|
951
|
-
|
|
952
|
-
### dvantages and Benefits 🌟
|
|
953
|
-
|
|
954
|
-
- **Flexible Submission**:
|
|
955
|
-
Choose the method that best suits backend requirements.
|
|
956
|
-
✅ _Better compatibility with various systems._
|
|
957
|
-
|
|
958
|
-
- **Automatic Normalization**:
|
|
959
|
-
Keys arriving `tags[]` format normalize to `tags` to simplify handling.
|
|
960
|
-
✅ _Easier iteration and conversion to original types._
|
|
961
|
-
|
|
962
|
-
- **Full URL Control**:
|
|
963
|
-
The hook consistently manages URL rewriting, reducing errors and keeping it readable.
|
|
964
|
-
🔒 _Improves parameter security and control._
|
|
965
|
-
|
|
966
|
-
### Code Usage Examples 👨💻
|
|
967
|
-
|
|
968
|
-
```jsx
|
|
969
|
-
import { useMagicSearchParams } from "useMagicSearchParams";
|
|
970
|
-
import { paramsUsers } from "../src/constants/defaulParamsPage";
|
|
971
|
-
|
|
972
|
-
export default function App() {
|
|
973
|
-
const { searchParams, getParams, updateParams, clearParams } = useMagicSearchParams({
|
|
974
|
-
...paramsUsers,
|
|
975
|
-
defaultParams: paramsUsers.mandatory,
|
|
976
|
-
arraySerialization: 'repeat', // You can switch to 'csv' or 'brackets' as needed.
|
|
977
|
-
omitParamsByValues: ["all", "default"],
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
// Get converted parameters (for example, tags is retrieved as an array)
|
|
981
|
-
const { tags, page } = getParams({ convert: true });
|
|
982
|
-
|
|
983
|
-
const availableTags = ['react', 'node', 'typescript', 'javascript'];
|
|
984
|
-
|
|
985
|
-
// Example: Update the tags array with toggle
|
|
986
|
-
const handleTagToggle = (newTag: string) => {
|
|
987
|
-
// if it exists, remove it; otherwise, add it
|
|
988
|
-
updateParams({ newParams: { tags: newTag } });
|
|
989
|
-
};
|
|
990
|
-
|
|
991
|
-
// Pass an array of tags, useful for adding multiple filters at once
|
|
992
|
-
const handleTagToggleArray = (newTags: string[]) => {
|
|
993
|
-
// the hook ensures no duplicates by merging with existing ones
|
|
994
|
-
updateParams({ newParams: { tags: [...tags, ...newTags] } });
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
return (
|
|
998
|
-
<div>
|
|
999
|
-
<div>
|
|
1000
|
-
<h3 className='text-lg font-semibold mb-3'>Select Tags:</h3>
|
|
1001
|
-
{availableTags.map(tag => {
|
|
1002
|
-
const isActive = Array.isArray(tags) && tags.includes(tag);
|
|
1003
|
-
return (
|
|
1004
|
-
<button
|
|
1005
|
-
key={tag}
|
|
1006
|
-
onClick={() => handleTagToggle(tag)}
|
|
1007
|
-
className={`px-4 py-2 rounded-md border ${
|
|
1008
|
-
isActive ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-700'
|
|
1009
|
-
}`}
|
|
1010
|
-
>
|
|
1011
|
-
{tag}
|
|
1012
|
-
</button>
|
|
1013
|
-
);
|
|
1014
|
-
})}
|
|
1015
|
-
</div>
|
|
1016
|
-
<p>Current tags: {JSON.stringify(tags)}</p>
|
|
1017
|
-
{/* Rest of the component */}
|
|
1018
|
-
</div>
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
```
|
|
1022
|
-
|
|
1023
|
-
In this example, when using **repeat** serialization the `URL` result look like this:
|
|
1024
|
-
|
|
1025
|
-
- **(repeat) Mode**: `?page=1&page_size=10&only_is_active=false&tags=tag1&tags=tag2&tags=tag3`
|
|
1026
|
-
- **(csv) Mode**: `?page=1&page_size=10&only_is_active=false&tags=tag1,tag2,tag3`
|
|
1027
|
-
- **(brackets) Mode**: `?page=1&page_size=10&only_is_active=false&tags[]=tag1&tags[]=tag2&tags[]=tag3`
|
|
1028
|
-
|
|
1029
|
-
### This new functionality allows you to:
|
|
1030
|
-
|
|
1031
|
-
- Send arrays in a format that matches backend expectations.
|
|
1032
|
-
- Centrally manage conversion and serialization, reducing complexity in individual components.
|
|
1033
|
-
- Keep the URL clean and consistent, no matter which serialization method you choose.
|
|
1034
|
-
- **Provide total control to the developer**: On how to transform or send parameters, allowing custom operations based on the backend.
|
|
1035
|
-
|
|
1036
|
-
### Why Is This Functionality Key? 🎯
|
|
1037
|
-
|
|
1038
|
-
- **Adaptable array submission to the backend:**
|
|
1039
|
-
Fits various formats servers may expect.
|
|
1040
|
-
- **Reduced complexity in components:**
|
|
1041
|
-
Centralizes serialization logic, preventing code duplication.
|
|
1042
|
-
- **Better user experience:**
|
|
1043
|
-
A clean, consistent URL makes debugging easier and improves usability.
|
|
1044
|
-
|
|
1045
|
-
## 8 Best Practices and Considerations ✅
|
|
1046
|
-
|
|
1047
|
-
1. **Validate sensitive parameters in the backend**: Even though the hook protects on the frontend, the server must enforce its own limits.
|
|
1048
|
-
2. **Keep types updated:**: As requirements change, update `mandatory` and `optional` to avoid mismatches.
|
|
1049
|
-
3. **One constants file per view**: Helps organize each screen or section, keeping clarity and consistency.
|
|
78
|
+
- `getParams(opts?)` → returns typed params.
|
|
79
|
+
- `updateParams({ newParams, keepParams? })` → updates the URL.
|
|
80
|
+
- `clearParams({ keepMandatoryParams? })` → clears parameters.
|
|
81
|
+
- `onChange(key, callbacks[])` → subscribes to parameter changes.
|
|
1050
82
|
|
|
1051
83
|
---
|
|
1052
84
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
This project includes automated tests to ensure its robustness and reliability.
|
|
1056
|
-
|
|
1057
|
-
### Run your tests with **Vitest** 🧪
|
|
85
|
+
### 🔄 Array Serialization
|
|
1058
86
|
|
|
1059
|
-
|
|
87
|
+
| Method | Example URL |
|
|
88
|
+
|------------|--------------------------------------|
|
|
89
|
+
| `csv` | `tags=tag1,tag2,tag3` |
|
|
90
|
+
| `repeat` | `tags=tag1&tags=tag2&tags=tag3` |
|
|
91
|
+
| `brackets` | `tags[]=tag1&tags[]=tag2&tags[]=tag3` |
|
|
1060
92
|
|
|
1061
|
-
|
|
1062
|
-
npm run test ./test/useMagicSearchParams.test.ts
|
|
1063
|
-
```
|
|
1064
|
-
|
|
1065
|
-
> [!WARNING]
|
|
1066
|
-
> Note: Make sure you have Vitest configured in your project so these tests can run correctly; you can check the version with `npm list`
|
|
1067
|
-
|
|
1068
|
-
## 10 Conclusion 🎉
|
|
1069
|
-
|
|
1070
|
-
The `useMagicSearchParams` hook provides:
|
|
1071
|
-
|
|
1072
|
-
- **Readability and Maintainability**: By centralizing logic:
|
|
1073
|
-
- **Robustess**: in parameter management, limiting insecure values and enabling a coherent flow.
|
|
1074
|
-
|
|
1075
|
-
It’s recommended to adjust or expand it based on each project’s needs, for example by adding advanced validations or additional type conversions.
|
|
1076
|
-
|
|
1077
|
-
---
|
|
93
|
+
## 🔗 Complete Documentation
|
|
1078
94
|
|
|
1079
|
-
|
|
1080
|
-
|
|
95
|
+
Find the complete guide and examples on GitHub:
|
|
96
|
+
https://github.com/Gabriel117343/react-magic-search-params
|
|
1081
97
|
|
|
1082
|
-
|
|
1083
|
-
<img src="https://github.com/user-attachments/assets/acd13a47-dcd3-488c-b0be-69ce466bb106" alt="Captura de pantalla" width="500px" />
|
|
1084
|
-
</div>
|
|
98
|
+
If you found this library helpful, please give it a ⭐️ on GitHub!
|