react-magic-search-params 1.0.1 → 1.1.3
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 +514 -7
- package/dist/index.d.ts +1 -1
- package/dist/react-magic-search-params.cjs.development.js +25 -8
- package/dist/react-magic-search-params.cjs.development.js.map +1 -1
- package/dist/react-magic-search-params.cjs.production.min.js +1 -1
- package/dist/react-magic-search-params.cjs.production.min.js.map +1 -1
- package/dist/react-magic-search-params.esm.js +25 -8
- package/dist/react-magic-search-params.esm.js.map +1 -1
- package/package.json +7 -2
- package/src/constants/defaultParamsPage.ts +1 -1
- package/src/index.d.ts +0 -1
- package/src/index.tsx +1 -1
- package/src/useMagicSearchParams.ts +27 -6
package/README.md
CHANGED
|
@@ -17,6 +17,507 @@ To install this library, run:
|
|
|
17
17
|
npm install react-magic-search-params
|
|
18
18
|
```
|
|
19
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) 🎯
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
# Introducción General
|
|
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 });
|
|
424
|
+
|
|
425
|
+
const availableTags = ['react', 'node', 'typescript', 'javascript']
|
|
426
|
+
|
|
427
|
+
// Ejemplo: Actualizar el array de tags con toggle
|
|
428
|
+
const handleTagToggle = (newTag: string) => {
|
|
429
|
+
// si ya existe se elimina, sino se agrega
|
|
430
|
+
updateParams({ newParams: { tags: newTag } });
|
|
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.
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## 9 Pruebas Unitarias 🔬
|
|
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:
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
npm run test ./test/useMagicSearchParams.test.ts
|
|
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
|
+
</details>
|
|
520
|
+
|
|
20
521
|
## Table of Contents 📑
|
|
21
522
|
|
|
22
523
|
1. [General Introduction](#general-introduction)
|
|
@@ -56,7 +557,7 @@ It allows you to define and unify logic to filter, paginate, or perform any othe
|
|
|
56
557
|
This section quickly illustrates how parameter handling changed before using the hook and how it simplifies with `useMagicSearchParams`.
|
|
57
558
|
|
|
58
559
|
<details>
|
|
59
|
-
<summary>Before (manual URL handling) ❌</summary>
|
|
560
|
+
<summary><strong>Before (manual URL handling) ❌</strong></summary>
|
|
60
561
|
|
|
61
562
|
```jsx
|
|
62
563
|
// filepath: /example/BeforeHook.tsx
|
|
@@ -87,7 +588,8 @@ export const BeforeHookExample = () => {
|
|
|
87
588
|
```
|
|
88
589
|
|
|
89
590
|
</details>
|
|
90
|
-
|
|
591
|
+
|
|
592
|
+
</details> <details> <summary><strong>After (with autocomplete and safety) ✅</strong></summary>
|
|
91
593
|
|
|
92
594
|
```jsx
|
|
93
595
|
// filepath: /example/AfterHook.tsx
|
|
@@ -151,6 +653,9 @@ export const AfterHookExample = () => {
|
|
|
151
653
|
|
|
152
654
|
</details>
|
|
153
655
|
|
|
656
|
+
> [!NOTE]
|
|
657
|
+
> 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.
|
|
658
|
+
|
|
154
659
|
#### Additional Information 📋
|
|
155
660
|
|
|
156
661
|
1. **Strict Typing_**
|
|
@@ -183,7 +688,7 @@ export const AfterHookExample = () => {
|
|
|
183
688
|
- Typical example: Pagination (page, page_size)
|
|
184
689
|
- They must always exist in the URL for the view to work.
|
|
185
690
|
|
|
186
|
-
2. **Optional**:
|
|
691
|
+
2. **Optional**:
|
|
187
692
|
|
|
188
693
|
- Example: Search filters (search, order).
|
|
189
694
|
- They do not affect the **route** if they are not present.
|
|
@@ -199,7 +704,7 @@ export const AfterHookExample = () => {
|
|
|
199
704
|
- Enforce values that cannot be overwritten (e.g. page_size=10).
|
|
200
705
|
- Provides maximum safety while enhancing user experience (avoid page_size=1000).
|
|
201
706
|
|
|
202
|
-
5. **OmitParamsByValues**:(
|
|
707
|
+
5. **OmitParamsByValues**:(Parameters Omitted by values)
|
|
203
708
|
|
|
204
709
|
- A list of values that, if detected, are omitted from the **URL** (e.g. ‘all’, ‘default’).
|
|
205
710
|
- Simplifies URLs by removing parameters that do not provide real information.
|
|
@@ -245,6 +750,8 @@ export const paramsUsers = {
|
|
|
245
750
|
},
|
|
246
751
|
};
|
|
247
752
|
```
|
|
753
|
+
> [!TIP]
|
|
754
|
+
> 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.
|
|
248
755
|
|
|
249
756
|
## 4 Main Functions ⚙️
|
|
250
757
|
|
|
@@ -257,7 +764,7 @@ Useful to pull `“page”, “order”, “search”`, etc. without dealing wit
|
|
|
257
764
|
> 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.
|
|
258
765
|
|
|
259
766
|
<details>
|
|
260
|
-
<summary>
|
|
767
|
+
<summary>Example of use</summary>
|
|
261
768
|
|
|
262
769
|
```typescript
|
|
263
770
|
// Retrieving converted values
|
|
@@ -277,7 +784,7 @@ console.log("Search:", search); // string | undefined
|
|
|
277
784
|
Safely modifies URL parameters, respecting mandatory values; you may reset one value without losing the rest (e.g. set `page=1` while keeping `search`).
|
|
278
785
|
|
|
279
786
|
<details>
|
|
280
|
-
<summary>
|
|
787
|
+
<summary>Example of use</summary>
|
|
281
788
|
|
|
282
789
|
```typescript
|
|
283
790
|
// Change page and keep the current order
|
|
@@ -296,7 +803,7 @@ updateParams({ newParams: { page: 1, search: "John" } });
|
|
|
296
803
|
|
|
297
804
|
Resets the URL parameters, optionally keeping mandatory ones.
|
|
298
805
|
Allows you to “clear” the filters and return to the initial state.
|
|
299
|
-
<details> <summary>
|
|
806
|
+
<details> <summary>Example of use</summary>
|
|
300
807
|
|
|
301
808
|
```typescript
|
|
302
809
|
// Clear everything and keep mandatory parameters
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { useMagicSearchParams } from './useMagicSearchParams';
|
|
1
|
+
export { useMagicSearchParams, UseMagicSearchParamsOptions } from './useMagicSearchParams';
|
|
@@ -166,9 +166,6 @@ var useMagicSearchParams = function useMagicSearchParams(_ref) {
|
|
|
166
166
|
updatedParams[key] = combined;
|
|
167
167
|
}
|
|
168
168
|
});
|
|
169
|
-
console.log({
|
|
170
|
-
updatedParams: updatedParams
|
|
171
|
-
});
|
|
172
169
|
return updatedParams;
|
|
173
170
|
};
|
|
174
171
|
var transformParamsToURLSearch = function transformParamsToURLSearch(params) {
|
|
@@ -222,9 +219,6 @@ var useMagicSearchParams = function useMagicSearchParams(_ref) {
|
|
|
222
219
|
newParam.set(key, params[key]);
|
|
223
220
|
}
|
|
224
221
|
}
|
|
225
|
-
console.log({
|
|
226
|
-
FINAL: newParam.toString()
|
|
227
|
-
});
|
|
228
222
|
return newParam;
|
|
229
223
|
};
|
|
230
224
|
// @ts-ignore
|
|
@@ -367,6 +361,11 @@ var useMagicSearchParams = function useMagicSearchParams(_ref) {
|
|
|
367
361
|
var CURRENT_PARAMS_URL = react.useMemo(function () {
|
|
368
362
|
return arraySerialization === 'brackets' ? getParamsObj(searchParams) : Object.fromEntries(searchParams.entries());
|
|
369
363
|
}, [searchParams, arraySerialization]);
|
|
364
|
+
/**
|
|
365
|
+
* Gets the current parameters from the URL and converts them to their original type if desired
|
|
366
|
+
* @param convert - If true, converts from string to the inferred type (number, boolean, ...)
|
|
367
|
+
* @returns - Returns the current parameters of the URL
|
|
368
|
+
*/
|
|
370
369
|
var getParams = function getParams(_temp) {
|
|
371
370
|
var _ref4 = _temp === void 0 ? {} : _temp,
|
|
372
371
|
_ref4$convert = _ref4.convert,
|
|
@@ -382,6 +381,12 @@ var useMagicSearchParams = function useMagicSearchParams(_ref) {
|
|
|
382
381
|
}, {});
|
|
383
382
|
return params;
|
|
384
383
|
};
|
|
384
|
+
/**
|
|
385
|
+
* Gets the value of a parameter from the URL and converts it to its original type if desired
|
|
386
|
+
* @param key - Key of the parameter
|
|
387
|
+
* @param options - Options to convert the value to its original type, default is true
|
|
388
|
+
* @returns - Returns the value of the parameter
|
|
389
|
+
*/
|
|
385
390
|
var getParam = function getParam(key, options) {
|
|
386
391
|
var keyStr = String(key);
|
|
387
392
|
// @ts-ignore
|
|
@@ -439,6 +444,10 @@ var useMagicSearchParams = function useMagicSearchParams(_ref) {
|
|
|
439
444
|
}, {});
|
|
440
445
|
return paramsUrlFound;
|
|
441
446
|
};
|
|
447
|
+
/**
|
|
448
|
+
clears the parameters of the URL, keeping the mandatory parameters
|
|
449
|
+
* @param keepMandatoryParams - If true, the mandatory parameters are kept in the URL
|
|
450
|
+
*/
|
|
442
451
|
var clearParams = function clearParams(_temp2) {
|
|
443
452
|
var _ref6 = _temp2 === void 0 ? {} : _temp2,
|
|
444
453
|
_ref6$keepMandatoryPa = _ref6.keepMandatoryParams,
|
|
@@ -447,6 +456,11 @@ var useMagicSearchParams = function useMagicSearchParams(_ref) {
|
|
|
447
456
|
var paramsTransformed = transformParamsToURLSearch(_extends({}, mandatory, keepMandatoryParams && _extends({}, mandatoryParameters()), forceParams));
|
|
448
457
|
setSearchParams(paramsTransformed);
|
|
449
458
|
};
|
|
459
|
+
/**
|
|
460
|
+
Merges the new parameters with the current ones, omits the parameters that are not sent and sorts them according to the structure
|
|
461
|
+
* @param newParams - New parameters to be sent in the URL
|
|
462
|
+
* @param keepParams - Parameters to keep in the URL, default is true
|
|
463
|
+
*/
|
|
450
464
|
var updateParams = function updateParams(_temp3) {
|
|
451
465
|
var _ref7 = _temp3 === void 0 ? {} : _temp3,
|
|
452
466
|
_ref7$newParams = _ref7.newParams,
|
|
@@ -463,7 +477,11 @@ var useMagicSearchParams = function useMagicSearchParams(_ref) {
|
|
|
463
477
|
var paramsSorted = sortParameters(convertedArrayValues);
|
|
464
478
|
setSearchParams(transformParamsToURLSearch(paramsSorted));
|
|
465
479
|
};
|
|
466
|
-
|
|
480
|
+
/**
|
|
481
|
+
* @param paramName - Name of the parameter to subscribe to
|
|
482
|
+
* @param callbacks - Callbacks to be executed when the parameter changes
|
|
483
|
+
* @returns - Returns the function to unsubscribe
|
|
484
|
+
*/
|
|
467
485
|
var onChange = react.useCallback(function (paramName, callbacks) {
|
|
468
486
|
var paramNameStr = String(paramName);
|
|
469
487
|
// replace the previous callbacks with the new ones so as not to accumulate callbacks
|
|
@@ -481,7 +499,6 @@ var useMagicSearchParams = function useMagicSearchParams(_ref) {
|
|
|
481
499
|
if (newValue !== oldValue) {
|
|
482
500
|
for (var _iterator4 = _createForOfIteratorHelperLoose(value), _step4; !(_step4 = _iterator4()).done;) {
|
|
483
501
|
var callback = _step4.value;
|
|
484
|
-
console.log(value);
|
|
485
502
|
callback();
|
|
486
503
|
}
|
|
487
504
|
}
|