state-in-url 6.4.0 → 6.5.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.es.md +146 -4
- package/README.fr.md +146 -4
- package/README.ja.md +146 -4
- package/README.ko.md +146 -4
- package/README.md +144 -3
- package/README.pt-BR.md +146 -4
- package/README.ru.md +146 -4
- package/README.vi.md +146 -4
- package/README.zh-CN.md +146 -4
- package/dist/astro/index.d.ts +1 -0
- package/dist/astro/index.js +1 -0
- package/dist/astro/index.mjs +1 -0
- package/dist/astro/useUrlState/index.d.ts +1 -0
- package/dist/astro/useUrlState/index.js +1 -0
- package/dist/astro/useUrlState/index.mjs +1 -0
- package/dist/astro/useUrlState/useUrlState.d.ts +85 -0
- package/dist/astro/useUrlState/useUrlState.js +1 -0
- package/dist/astro/useUrlState/useUrlState.mjs +1 -0
- package/dist/encodeState/encodeState.d.ts +1 -1
- package/dist/encodeState/encodeState.js +1 -1
- package/dist/encodeState/encodeState.mjs +1 -1
- package/dist/next/useUrlState/useUrlState.d.ts +3 -3
- package/dist/next/useUrlState/useUrlState.js +1 -1
- package/dist/next/useUrlState/useUrlState.mjs +1 -1
- package/dist/package.json +89 -39
- package/dist/react-router/useUrlState/useUrlState.d.ts +5 -5
- package/dist/react-router/useUrlState/useUrlState.js +1 -1
- package/dist/react-router/useUrlState/useUrlState.mjs +1 -1
- package/dist/react-router6/useUrlState/useUrlState.d.ts +7 -7
- package/dist/react-router6/useUrlState/useUrlState.js +1 -1
- package/dist/remix/useUrlState/useUrlState.d.ts +5 -5
- package/dist/useIsomorphicLayoutEffect.d.ts +2 -0
- package/dist/useIsomorphicLayoutEffect.js +1 -0
- package/dist/useIsomorphicLayoutEffect.mjs +1 -0
- package/dist/useLinkProps/useLinkProps.mjs +1 -1
- package/dist/useSharedState/useSharedState.js +1 -1
- package/dist/useSharedState/useSharedState.mjs +1 -1
- package/dist/useUrlEncode/useUrlEncode.js +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.js +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.mjs +1 -1
- package/package.json +89 -39
- package/skills/astro-setup/SKILL.md +204 -0
- package/skills/feature-state-hook/SKILL.md +1 -1
- package/skills/form-library-integration/SKILL.md +1 -1
- package/skills/input-handling/SKILL.md +1 -1
- package/skills/nextjs-ssr/SKILL.md +1 -1
- package/skills/react-router-remix-setup/SKILL.md +1 -1
- package/skills/shared-state-no-url/SKILL.md +1 -1
- package/dist/useInsertionEffect.d.ts +0 -2
- package/dist/useInsertionEffect.js +0 -1
- package/dist/useInsertionEffect.mjs +0 -1
package/README.es.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!-- i18n:start -->
|
|
2
2
|
[English](./README.md) · [简体中文](./README.zh-CN.md) · [日本語](./README.ja.md) · [한국어](./README.ko.md) · [Русский](./README.ru.md) · Español · [Português (BR)](./README.pt-BR.md) · [Français](./README.fr.md) · [Tiếng Việt](./README.vi.md)
|
|
3
|
-
<!-- i18n:meta locale=es source=README.md source-blob=
|
|
3
|
+
<!-- i18n:meta locale=es source=README.md source-blob=4d98ba970e6aab8f29987fbcba29fb6c61c2fc67 status=translated -->
|
|
4
4
|
<!-- i18n:end -->
|
|
5
5
|
|
|
6
6
|
<div align="center">
|
|
@@ -69,7 +69,7 @@ Comparte si te resulta útil.
|
|
|
69
69
|
Guarda cualquier estado de usuario en los parámetros de consulta; imagina JSON en la URL de un navegador. Todo ello manteniendo los tipos y la estructura de los datos: por ejemplo, los números se decodificarán como números y no como cadenas, las fechas como fechas, etc.; se admiten objetos y matrices.
|
|
70
70
|
Simple a más no poder, rápido y con validación estática de TypeScript. Los enlaces profundos, es decir, la sincronización de URL, se vuelven fáciles.
|
|
71
71
|
|
|
72
|
-
Incluye el hook `useUrlState` para Next.js
|
|
72
|
+
Incluye el hook `useUrlState` para Next.js, react-router, Remix y Astro, y helpers para cualquier otra cosa en JS.
|
|
73
73
|
Como los navegadores modernos soportan URLs enormes y a los usuarios no les importan las cadenas de consulta (es un flujo de seleccionar todo y copiar/pegar).
|
|
74
74
|
|
|
75
75
|
Es hora de usar la cadena de consulta para gestionar el estado, como se pensó originalmente.
|
|
@@ -98,10 +98,32 @@ Esta biblioteca es una buena alternativa a NUQS.
|
|
|
98
98
|
- **Renderizado en el servidor**: Se puede usar en componentes de servidor, se soportan Next.js 14, 15 y 16
|
|
99
99
|
- **Ligero**: Cero dependencias, biblioteca de menos de 2KB
|
|
100
100
|
- **DX**: Buena experiencia de desarrollo, documentación, comentarios JSDoc y ejemplos
|
|
101
|
-
- **Flexibilidad de frameworks**: Hooks para `Next.js
|
|
101
|
+
- **Flexibilidad de frameworks**: Hooks para `Next.js`, `react-router`, `Remix` y `Astro`, helpers para usarla con otros frameworks o JS puro
|
|
102
102
|
- **Bien probada**: [Pruebas unitarias y pruebas de Playwright para Chrome/Firefox/Safari](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml)
|
|
103
103
|
- **Licencia permisiva**: MIT
|
|
104
104
|
|
|
105
|
+
### Comparación con nuqs
|
|
106
|
+
|
|
107
|
+
¿Buscas una alternativa a [nuqs](https://github.com/47ng/nuqs)? Ambas guardan estado tipado en la query string; difieren en cuánto hay que configurar y en qué puede ser un valor.
|
|
108
|
+
|
|
109
|
+
| Qué | `state-in-url` | `nuqs` |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| Configuración | Ninguna — importa el hook y listo | Un componente adaptador envuelve la app |
|
|
112
|
+
| Forma del estado | Un objeto tipado, como `React.useState` | Valores por clave, con un parser declarado para cada una |
|
|
113
|
+
| Reutilización entre componentes | Envuelve el hook una vez — cada componente comparte el estado, sin props | Extraes tu propio hook alrededor del mapa de parsers |
|
|
114
|
+
| Objetos y arrays anidados | Integrado — estructura y tipos se conservan | Parser JSON más tu propio validador |
|
|
115
|
+
| Fechas | Se conservan automáticamente | Parser integrado, declarado por clave |
|
|
116
|
+
| Tamaño, import completo | ~2,9 KB gzip | ~6,7 KB gzip |
|
|
117
|
+
| Dependencias en runtime | Ninguna | Una |
|
|
118
|
+
| Routers | Next.js, React Router v6/v7, Remix, Astro, helpers para JS puro | Next.js, React Router, Remix, TanStack Router, React puro |
|
|
119
|
+
|
|
120
|
+
Tamaños: import de toda la librería, esbuild minify + gzip, medido en agosto de 2026 contra nuqs 2.10.1.
|
|
121
|
+
|
|
122
|
+
nuqs es una buena librería: elígela si quieres cada valor como su propio query param legible o usas TanStack Router. Elige state-in-url cuando quieras un objeto tipado completo en la URL sin configuración.
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
La comparación completa — la misma feature en ambas, otras alternativas (TanStack Router, use-query-params) y notas de migración — está en <https://state-in-url.dev/vs/nuqs>.
|
|
126
|
+
|
|
105
127
|
## Tabla de contenido
|
|
106
128
|
|
|
107
129
|
<!-- toc:start -->
|
|
@@ -111,6 +133,7 @@ Esta biblioteca es una buena alternativa a NUQS.
|
|
|
111
133
|
- [¿Por qué usar `state-in-url`?](#por-qué-usar-state-in-url)
|
|
112
134
|
- [Casos de uso](#casos-de-uso)
|
|
113
135
|
- [Características](#características)
|
|
136
|
+
- [Comparación con nuqs](#comparación-con-nuqs)
|
|
114
137
|
- [Tabla de contenido](#tabla-de-contenido)
|
|
115
138
|
- [Instalación](#instalación)
|
|
116
139
|
- [1. Instalar el paquete](#1-instalar-el-paquete)
|
|
@@ -127,6 +150,8 @@ Esta biblioteca es una buena alternativa a NUQS.
|
|
|
127
150
|
- [Ejemplo](#ejemplo)
|
|
128
151
|
- [Hook useUrlState para React-Router](#hook-useurlstate-para-react-router)
|
|
129
152
|
- [Ejemplo](#ejemplo-1)
|
|
153
|
+
- [Hook useUrlState para Astro](#hook-useurlstate-para-astro)
|
|
154
|
+
- [Ejemplo](#ejemplo-2)
|
|
130
155
|
- [Recetas](#recetas)
|
|
131
156
|
- [Hook personalizado para trabajar cómodamente con una porción del estado](#hook-personalizado-para-trabajar-cómodamente-con-una-porción-del-estado)
|
|
132
157
|
- [Con una forma de estado compleja](#con-una-forma-de-estado-compleja)
|
|
@@ -515,6 +540,123 @@ const tags = [
|
|
|
515
540
|
|
|
516
541
|
[Ejemplo de código](packages/example-react-router6/src/Form-for-test.tsx)
|
|
517
542
|
|
|
543
|
+
### Hook useUrlState para Astro
|
|
544
|
+
|
|
545
|
+
Para islas de React. Astro no tiene router del lado del cliente por defecto, así que el hook escribe la URL con `window.history` y la vuelve a leer al navegar atrás/adelante y en cualquier otro `pushState`/`replaceState`, incluido el propio `<ClientRouter />` de Astro. Las islas de una página comparten el estado, sin nada que las envuelva.
|
|
546
|
+
|
|
547
|
+
[Documentación de la API](packages/urlstate/astro/useUrlState)
|
|
548
|
+
|
|
549
|
+
#### Ejemplo
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// src/state.ts
|
|
553
|
+
export const form: Form = {
|
|
554
|
+
name: '',
|
|
555
|
+
age: undefined,
|
|
556
|
+
agree_to_terms: false,
|
|
557
|
+
tags: [],
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
type Form = {
|
|
561
|
+
name: string;
|
|
562
|
+
age?: number;
|
|
563
|
+
agree_to_terms: boolean;
|
|
564
|
+
tags: { id: string; value: { text: string; time: Date } }[];
|
|
565
|
+
};
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
La página debe renderizarse bajo demanda (`output: 'server'`, o `export const prerender = false` en la página, con un adaptador): una página prerenderizada no tiene petición, así que la isla recibe `{}` y lee la URL después de la hidratación.
|
|
569
|
+
|
|
570
|
+
```astro
|
|
571
|
+
---
|
|
572
|
+
// src/pages/index.astro
|
|
573
|
+
import { Form } from '../components/Form';
|
|
574
|
+
import { Status } from '../components/Status';
|
|
575
|
+
|
|
576
|
+
// El renderizado en el servidor coincide con la URL, así que la hidratación no tiene nada que corregir.
|
|
577
|
+
// Un objeto plano: las props de las islas se serializan, URLSearchParams no.
|
|
578
|
+
const searchParams = Object.fromEntries(Astro.url.searchParams);
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
<Form client:load searchParams={searchParams} />
|
|
582
|
+
<Status client:load searchParams={searchParams} />
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
// src/components/Form.tsx
|
|
587
|
+
import React from 'react';
|
|
588
|
+
import { useUrlState } from 'state-in-url/astro';
|
|
589
|
+
|
|
590
|
+
import { form } from '../state';
|
|
591
|
+
|
|
592
|
+
export function Form({ searchParams }: { searchParams?: Record<string, string> }) {
|
|
593
|
+
const { urlState, setUrl, setState } = useUrlState(form, { searchParams });
|
|
594
|
+
|
|
595
|
+
const onChangeTags = React.useCallback(
|
|
596
|
+
(tag: (typeof tags)[number]) => {
|
|
597
|
+
setUrl((curr) => ({
|
|
598
|
+
...curr,
|
|
599
|
+
tags: curr.tags.find((t) => t.id === tag.id)
|
|
600
|
+
? curr.tags.filter((t) => t.id !== tag.id)
|
|
601
|
+
: curr.tags.concat(tag),
|
|
602
|
+
}));
|
|
603
|
+
},
|
|
604
|
+
[setUrl],
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
return (
|
|
608
|
+
<div>
|
|
609
|
+
{tags.map((tag) => (
|
|
610
|
+
<Tag
|
|
611
|
+
active={!!urlState.tags.find((t) => t.id === tag.id)}
|
|
612
|
+
text={tag.value.text}
|
|
613
|
+
onClick={() => onChangeTags(tag)}
|
|
614
|
+
key={tag.id}
|
|
615
|
+
/>
|
|
616
|
+
))}
|
|
617
|
+
|
|
618
|
+
<input value={urlState.name}
|
|
619
|
+
onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
|
|
620
|
+
// Puede actualizar el estado inmediatamente pero sincronizar el cambio con la url según sea necesario
|
|
621
|
+
onBlur={() => setUrl()}
|
|
622
|
+
/>
|
|
623
|
+
</div>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const tags = [
|
|
628
|
+
{ id: '1', value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') } },
|
|
629
|
+
{ id: '2', value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') } },
|
|
630
|
+
{ id: '3', value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') } },
|
|
631
|
+
];
|
|
632
|
+
|
|
633
|
+
// Status.tsx, una segunda isla, lee el mismo estado
|
|
634
|
+
export function Status({ searchParams }: { searchParams?: Record<string, string> }) {
|
|
635
|
+
const { urlState } = useUrlState(form, { searchParams });
|
|
636
|
+
return <pre>{JSON.stringify(urlState, null, 2)}</pre>;
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Las islas de Preact funcionan igual: con `@astrojs/preact` y `compat: true`, `react` se resuelve a `preact/compat` tanto en la compilación del servidor como en la del cliente, y el import de arriba no cambia.
|
|
641
|
+
|
|
642
|
+
Sin islas, en una página sin ningún framework de cliente, el mismo estado vive en el frontmatter a través de [`decodeState` y `encodeState`](#helpers-encodestate-y-decodestate):
|
|
643
|
+
|
|
644
|
+
```astro
|
|
645
|
+
---
|
|
646
|
+
import { decodeState, encodeState } from 'state-in-url/encodeState';
|
|
647
|
+
|
|
648
|
+
import { form } from '../state';
|
|
649
|
+
|
|
650
|
+
const state = decodeState(Astro.url.searchParams, form);
|
|
651
|
+
const withName = encodeState({ ...state, name: 'Alice' }, form, Astro.url.searchParams);
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
<pre>{JSON.stringify(state)}</pre>
|
|
655
|
+
<a href={`?${withName}`}>Alice</a>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
[Ejemplo de código](packages/example-astro/src/components/Form-for-test.tsx), [página en Astro puro](packages/example-astro/src/pages/pure-astro.astro)
|
|
659
|
+
|
|
518
660
|
## Recetas
|
|
519
661
|
##### Hook personalizado para trabajar cómodamente con una porción del estado
|
|
520
662
|
<details>
|
|
@@ -787,7 +929,7 @@ Consulta el [documento de contribución](CONTRIBUTING.md)
|
|
|
787
929
|
- [x] hook para `react-router`
|
|
788
930
|
- [x] hook para `remix`
|
|
789
931
|
- [ ] hook para `svelte`
|
|
790
|
-
- [
|
|
932
|
+
- [x] hook para `astro`
|
|
791
933
|
- [ ] hook para guardar el estado en el hash ?
|
|
792
934
|
|
|
793
935
|
## Contacto y soporte
|
package/README.fr.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!-- i18n:start -->
|
|
2
2
|
[English](./README.md) · [简体中文](./README.zh-CN.md) · [日本語](./README.ja.md) · [한국어](./README.ko.md) · [Русский](./README.ru.md) · [Español](./README.es.md) · [Português (BR)](./README.pt-BR.md) · Français · [Tiếng Việt](./README.vi.md)
|
|
3
|
-
<!-- i18n:meta locale=fr source=README.md source-blob=
|
|
3
|
+
<!-- i18n:meta locale=fr source=README.md source-blob=4d98ba970e6aab8f29987fbcba29fb6c61c2fc67 status=translated -->
|
|
4
4
|
<!-- i18n:end -->
|
|
5
5
|
|
|
6
6
|
<div align="center">
|
|
@@ -69,7 +69,7 @@ Partagez si cela vous est utile.
|
|
|
69
69
|
Stockez n'importe quel état utilisateur dans les paramètres de requête ; imaginez du JSON dans une URL de navigateur. Tout cela en conservant les types et la structure des données : par exemple, les nombres seront décodés comme des nombres et non comme des chaînes, les dates comme des dates, etc., les objets et les tableaux sont pris en charge.
|
|
70
70
|
Extrêmement simple, rapide et avec une validation TypeScript statique. Les liens profonds, autrement dit la synchronisation d'URL, deviennent faciles.
|
|
71
71
|
|
|
72
|
-
Contient le hook `useUrlState` pour Next.js
|
|
72
|
+
Contient le hook `useUrlState` pour Next.js, react-router, Remix et Astro, ainsi que des helpers pour tout le reste en JS.
|
|
73
73
|
Comme les navigateurs modernes prennent en charge d'énormes URL et que les utilisateurs ne se soucient pas des chaînes de requête (c'est un flux de travail « tout sélectionner et copier/coller »).
|
|
74
74
|
|
|
75
75
|
Il est temps d'utiliser la chaîne de requête pour la gestion d'état, comme cela était prévu à l'origine.
|
|
@@ -98,10 +98,32 @@ Cette bibliothèque est une bonne alternative à NUQS.
|
|
|
98
98
|
- **Rendu côté serveur** : Utilisable dans les composants serveur, Next.js 14, 15 et 16 sont pris en charge
|
|
99
99
|
- **Léger** : Zéro dépendance, bibliothèque de moins de 2KB
|
|
100
100
|
- **DX** : Bonne expérience de développement, documentation, commentaires JSDoc et exemples
|
|
101
|
-
- **Flexibilité de frameworks** : Hooks pour `Next.js
|
|
101
|
+
- **Flexibilité de frameworks** : Hooks pour `Next.js`, `react-router`, `Remix` et `Astro`, helpers pour l'utiliser avec d'autres frameworks ou du JS pur
|
|
102
102
|
- **Bien testée** : [Tests unitaires et tests Playwright pour Chrome/Firefox/Safari](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml)
|
|
103
103
|
- **Licence permissive** : MIT
|
|
104
104
|
|
|
105
|
+
### Comparaison avec nuqs
|
|
106
|
+
|
|
107
|
+
Vous cherchez une alternative à [nuqs](https://github.com/47ng/nuqs) ? Les deux gardent un état typé dans la query string ; elles diffèrent par la configuration requise et par ce qu’une valeur peut être.
|
|
108
|
+
|
|
109
|
+
| Quoi | `state-in-url` | `nuqs` |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| Mise en place | Aucune — importez le hook et c’est parti | Un composant adaptateur enveloppe l’application |
|
|
112
|
+
| Forme de l’état | Un objet typé, comme `React.useState` | Valeurs par clé, un parseur déclaré pour chacune |
|
|
113
|
+
| Réutilisation entre composants | Enveloppez le hook une fois — chaque composant partage l’état, sans props | Vous extrayez votre propre hook autour de la table de parseurs |
|
|
114
|
+
| Objets et tableaux imbriqués | Intégré — structure et types préservés | Parseur JSON plus votre propre validateur |
|
|
115
|
+
| Dates | Préservées automatiquement | Parseur intégré, déclaré par clé |
|
|
116
|
+
| Taille, import complet | ~2,9 Ko gzip | ~6,7 Ko gzip |
|
|
117
|
+
| Dépendances au runtime | Aucune | Une |
|
|
118
|
+
| Routeurs | Next.js, React Router v6/v7, Remix, Astro, helpers pour JS pur | Next.js, React Router, Remix, TanStack Router, React pur |
|
|
119
|
+
|
|
120
|
+
Tailles : import de la bibliothèque entière, esbuild minify + gzip, mesuré en août 2026 face à nuqs 2.10.1.
|
|
121
|
+
|
|
122
|
+
nuqs est une bonne bibliothèque — choisissez-la pour un query param lisible par valeur, ou si vous êtes sur TanStack Router. Choisissez state-in-url pour un objet typé complet dans l’URL, sans configuration.
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
La comparaison complète — la même fonctionnalité dans les deux, d’autres alternatives (TanStack Router, use-query-params) et des notes de migration — se trouve sur <https://state-in-url.dev/vs/nuqs>.
|
|
126
|
+
|
|
105
127
|
## Table des matières
|
|
106
128
|
|
|
107
129
|
<!-- toc:start -->
|
|
@@ -111,6 +133,7 @@ Cette bibliothèque est une bonne alternative à NUQS.
|
|
|
111
133
|
- [Pourquoi utiliser `state-in-url` ?](#pourquoi-utiliser-state-in-url-)
|
|
112
134
|
- [Cas d'utilisation](#cas-dutilisation)
|
|
113
135
|
- [Fonctionnalités](#fonctionnalités)
|
|
136
|
+
- [Comparaison avec nuqs](#comparaison-avec-nuqs)
|
|
114
137
|
- [Table des matières](#table-des-matières)
|
|
115
138
|
- [Installation](#installation)
|
|
116
139
|
- [1. Installer le paquet](#1-installer-le-paquet)
|
|
@@ -127,6 +150,8 @@ Cette bibliothèque est une bonne alternative à NUQS.
|
|
|
127
150
|
- [Exemple](#exemple)
|
|
128
151
|
- [Hook useUrlState pour React-Router](#hook-useurlstate-pour-react-router)
|
|
129
152
|
- [Exemple](#exemple-1)
|
|
153
|
+
- [Hook useUrlState pour Astro](#hook-useurlstate-pour-astro)
|
|
154
|
+
- [Exemple](#exemple-2)
|
|
130
155
|
- [Recettes](#recettes)
|
|
131
156
|
- [Hook personnalisé pour travailler confortablement avec une tranche d'état](#hook-personnalisé-pour-travailler-confortablement-avec-une-tranche-détat)
|
|
132
157
|
- [Avec une forme d'état complexe](#avec-une-forme-détat-complexe)
|
|
@@ -515,6 +540,123 @@ const tags = [
|
|
|
515
540
|
|
|
516
541
|
[Exemple de code](packages/example-react-router6/src/Form-for-test.tsx)
|
|
517
542
|
|
|
543
|
+
### Hook useUrlState pour Astro
|
|
544
|
+
|
|
545
|
+
Pour les îlots React. Astro n'a pas de routeur côté client par défaut, donc le hook écrit l'URL avec `window.history` et la relit lors des navigations précédent/suivant et lors de tout autre `pushState`/`replaceState`, y compris le `<ClientRouter />` d'Astro. Les îlots d'une page partagent l'état, sans avoir à les envelopper dans quoi que ce soit.
|
|
546
|
+
|
|
547
|
+
[Documentation de l'API](packages/urlstate/astro/useUrlState)
|
|
548
|
+
|
|
549
|
+
#### Exemple
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// src/state.ts
|
|
553
|
+
export const form: Form = {
|
|
554
|
+
name: '',
|
|
555
|
+
age: undefined,
|
|
556
|
+
agree_to_terms: false,
|
|
557
|
+
tags: [],
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
type Form = {
|
|
561
|
+
name: string;
|
|
562
|
+
age?: number;
|
|
563
|
+
agree_to_terms: boolean;
|
|
564
|
+
tags: { id: string; value: { text: string; time: Date } }[];
|
|
565
|
+
};
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
La page doit être rendue à la demande (`output: 'server'`, ou `export const prerender = false` sur la page, avec un adaptateur) : une page prérendue n'a pas de requête, donc l'îlot reçoit `{}` et lit l'URL après l'hydratation.
|
|
569
|
+
|
|
570
|
+
```astro
|
|
571
|
+
---
|
|
572
|
+
// src/pages/index.astro
|
|
573
|
+
import { Form } from '../components/Form';
|
|
574
|
+
import { Status } from '../components/Status';
|
|
575
|
+
|
|
576
|
+
// Le rendu serveur correspond à l'URL, donc l'hydratation n'a rien à corriger.
|
|
577
|
+
// Un objet simple : les props d'îlot sont sérialisées, URLSearchParams ne l'est pas.
|
|
578
|
+
const searchParams = Object.fromEntries(Astro.url.searchParams);
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
<Form client:load searchParams={searchParams} />
|
|
582
|
+
<Status client:load searchParams={searchParams} />
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
// src/components/Form.tsx
|
|
587
|
+
import React from 'react';
|
|
588
|
+
import { useUrlState } from 'state-in-url/astro';
|
|
589
|
+
|
|
590
|
+
import { form } from '../state';
|
|
591
|
+
|
|
592
|
+
export function Form({ searchParams }: { searchParams?: Record<string, string> }) {
|
|
593
|
+
const { urlState, setUrl, setState } = useUrlState(form, { searchParams });
|
|
594
|
+
|
|
595
|
+
const onChangeTags = React.useCallback(
|
|
596
|
+
(tag: (typeof tags)[number]) => {
|
|
597
|
+
setUrl((curr) => ({
|
|
598
|
+
...curr,
|
|
599
|
+
tags: curr.tags.find((t) => t.id === tag.id)
|
|
600
|
+
? curr.tags.filter((t) => t.id !== tag.id)
|
|
601
|
+
: curr.tags.concat(tag),
|
|
602
|
+
}));
|
|
603
|
+
},
|
|
604
|
+
[setUrl],
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
return (
|
|
608
|
+
<div>
|
|
609
|
+
{tags.map((tag) => (
|
|
610
|
+
<Tag
|
|
611
|
+
active={!!urlState.tags.find((t) => t.id === tag.id)}
|
|
612
|
+
text={tag.value.text}
|
|
613
|
+
onClick={() => onChangeTags(tag)}
|
|
614
|
+
key={tag.id}
|
|
615
|
+
/>
|
|
616
|
+
))}
|
|
617
|
+
|
|
618
|
+
<input value={urlState.name}
|
|
619
|
+
onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
|
|
620
|
+
// Peut mettre à jour l'état immédiatement mais synchroniser le changement avec l'url selon les besoins
|
|
621
|
+
onBlur={() => setUrl()}
|
|
622
|
+
/>
|
|
623
|
+
</div>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const tags = [
|
|
628
|
+
{ id: '1', value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') } },
|
|
629
|
+
{ id: '2', value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') } },
|
|
630
|
+
{ id: '3', value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') } },
|
|
631
|
+
];
|
|
632
|
+
|
|
633
|
+
// Status.tsx, un second îlot, lit le même état
|
|
634
|
+
export function Status({ searchParams }: { searchParams?: Record<string, string> }) {
|
|
635
|
+
const { urlState } = useUrlState(form, { searchParams });
|
|
636
|
+
return <pre>{JSON.stringify(urlState, null, 2)}</pre>;
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Les îlots Preact fonctionnent de la même manière : avec `@astrojs/preact` et `compat: true`, `react` est résolu vers `preact/compat` aussi bien dans le build serveur que dans le build client, et l'import ci-dessus reste inchangé.
|
|
641
|
+
|
|
642
|
+
Sans îlots, sur une page sans aucun framework client, le même état vit dans le frontmatter via [`decodeState` et `encodeState`](#helpers-encodestate-et-decodestate) :
|
|
643
|
+
|
|
644
|
+
```astro
|
|
645
|
+
---
|
|
646
|
+
import { decodeState, encodeState } from 'state-in-url/encodeState';
|
|
647
|
+
|
|
648
|
+
import { form } from '../state';
|
|
649
|
+
|
|
650
|
+
const state = decodeState(Astro.url.searchParams, form);
|
|
651
|
+
const withName = encodeState({ ...state, name: 'Alice' }, form, Astro.url.searchParams);
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
<pre>{JSON.stringify(state)}</pre>
|
|
655
|
+
<a href={`?${withName}`}>Alice</a>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
[Exemple de code](packages/example-astro/src/components/Form-for-test.tsx), [page Astro pure](packages/example-astro/src/pages/pure-astro.astro)
|
|
659
|
+
|
|
518
660
|
## Recettes
|
|
519
661
|
##### Hook personnalisé pour travailler confortablement avec une tranche d'état
|
|
520
662
|
<details>
|
|
@@ -787,7 +929,7 @@ Voir le [document de contribution](CONTRIBUTING.md)
|
|
|
787
929
|
- [x] hook pour `react-router`
|
|
788
930
|
- [x] hook pour `remix`
|
|
789
931
|
- [ ] hook pour `svelte`
|
|
790
|
-
- [
|
|
932
|
+
- [x] hook pour `astro`
|
|
791
933
|
- [ ] hook pour stocker l'état dans le hash ?
|
|
792
934
|
|
|
793
935
|
## Contact et support
|
package/README.ja.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!-- i18n:start -->
|
|
2
2
|
[English](./README.md) · [简体中文](./README.zh-CN.md) · 日本語 · [한국어](./README.ko.md) · [Русский](./README.ru.md) · [Español](./README.es.md) · [Português (BR)](./README.pt-BR.md) · [Français](./README.fr.md) · [Tiếng Việt](./README.vi.md)
|
|
3
|
-
<!-- i18n:meta locale=ja source=README.md source-blob=
|
|
3
|
+
<!-- i18n:meta locale=ja source=README.md source-blob=4d98ba970e6aab8f29987fbcba29fb6c61c2fc67 status=translated -->
|
|
4
4
|
<!-- i18n:end -->
|
|
5
5
|
|
|
6
6
|
<div align="center">
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
任意のユーザー状態をクエリパラメータに保存します。ブラウザの URL に JSON があるイメージです。データの型と構造を保持したまま、たとえば数値は文字列ではなく数値として、日付は日付としてデコードされ、オブジェクトや配列もサポートされます。
|
|
70
70
|
非常にシンプルで高速、そして静的 TypeScript バリデーション付き。ディープリンク(別名 URL 同期)が簡単になります。
|
|
71
71
|
|
|
72
|
-
Next.js
|
|
72
|
+
Next.js、react-router、Remix、Astro 用の `useUrlState` フックと、その他あらゆる JS 向けのヘルパーを備えています。
|
|
73
73
|
現代のブラウザは巨大な URL をサポートしており、ユーザーはクエリ文字列を気にしません(全選択してコピー/ペーストするワークフローです)。
|
|
74
74
|
|
|
75
75
|
本来の目的どおりに、クエリ文字列を状態管理に使うときが来ました。
|
|
@@ -98,10 +98,32 @@ Next.js と react-router 用の `useUrlState` フックと、その他あらゆ
|
|
|
98
98
|
- **サーバーサイドレンダリング**: サーバーコンポーネントで使用可能。Next.js 14、15、16 をサポート
|
|
99
99
|
- **軽量**: 依存関係ゼロ。ライブラリは 2KB 未満
|
|
100
100
|
- **DX**: 優れた開発者体験。ドキュメント、JSDoc コメント、サンプル付き
|
|
101
|
-
- **フレームワークの柔軟性**: `Next.js
|
|
101
|
+
- **フレームワークの柔軟性**: `Next.js`、`react-router`、`Remix`、`Astro` 用のフック、その他のフレームワークやピュア JS で使うためのヘルパー
|
|
102
102
|
- **十分にテスト済み**: [Chrome/Firefox/Safari 向けのユニットテストと Playwright テスト](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml)
|
|
103
103
|
- **寛容なライセンス**: MIT
|
|
104
104
|
|
|
105
|
+
### nuqs との比較
|
|
106
|
+
|
|
107
|
+
[nuqs](https://github.com/47ng/nuqs) の代替をお探しですか?どちらも型付きの状態をクエリ文字列に保存しますが、必要な設定量と、値として扱えるものが異なります。
|
|
108
|
+
|
|
109
|
+
| 項目 | `state-in-url` | `nuqs` |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| セットアップ | 不要 — hook を import するだけ | アダプターコンポーネントでアプリをラップ |
|
|
112
|
+
| 状態の形 | `React.useState` のような型付きオブジェクト1つ | キーごとの値、それぞれにパーサーを宣言 |
|
|
113
|
+
| コンポーネント間の再利用 | hook を一度包むだけ — 全コンポーネントが状態を共有、props 不要 | パーサー群を包む hook は自分で抽出 |
|
|
114
|
+
| ネストしたオブジェクトと配列 | 標準対応 — 構造と型を保持 | JSON パーサーに加えて自前のバリデーターが必要 |
|
|
115
|
+
| 日付 | 自動的に保持 | 組み込みパーサーをキーごとに宣言 |
|
|
116
|
+
| サイズ(全体 import) | 約 2.9 KB gzip | 約 6.7 KB gzip |
|
|
117
|
+
| ランタイム依存 | なし | 1つ |
|
|
118
|
+
| ルーター | Next.js、React Router v6/v7、Remix、Astro、素の JS 用ヘルパー | Next.js、React Router、Remix、TanStack Router、素の React |
|
|
119
|
+
|
|
120
|
+
サイズはライブラリ全体の import を esbuild minify + gzip で計測(2026年8月、nuqs 2.10.1 と比較)。
|
|
121
|
+
|
|
122
|
+
nuqs も優れたライブラリです。値ごとに読みやすいクエリパラメータが欲しいとき、TanStack Router を使っているときは nuqs を。型付きオブジェクトを丸ごと URL に、設定ゼロで入れたいなら state-in-url を選んでください。
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
完全版の比較 — 同じ機能の両実装、ほかの代替(TanStack Router、use-query-params)、移行メモ — は <https://state-in-url.dev/vs/nuqs> にあります。
|
|
126
|
+
|
|
105
127
|
## 目次
|
|
106
128
|
|
|
107
129
|
<!-- toc:start -->
|
|
@@ -111,6 +133,7 @@ Next.js と react-router 用の `useUrlState` フックと、その他あらゆ
|
|
|
111
133
|
- [なぜ `state-in-url` を使うのか?](#なぜ-state-in-url-を使うのか)
|
|
112
134
|
- [ユースケース](#ユースケース)
|
|
113
135
|
- [特徴](#特徴)
|
|
136
|
+
- [nuqs との比較](#nuqs-との比較)
|
|
114
137
|
- [目次](#目次)
|
|
115
138
|
- [インストール](#インストール)
|
|
116
139
|
- [1. パッケージのインストール](#1-パッケージのインストール)
|
|
@@ -127,6 +150,8 @@ Next.js と react-router 用の `useUrlState` フックと、その他あらゆ
|
|
|
127
150
|
- [例](#例)
|
|
128
151
|
- [React-Router 向け useUrlState フック](#react-router-向け-useurlstate-フック)
|
|
129
152
|
- [例](#例-1)
|
|
153
|
+
- [Astro 向け useUrlState フック](#astro-向け-useurlstate-フック)
|
|
154
|
+
- [例](#例-2)
|
|
130
155
|
- [レシピ](#レシピ)
|
|
131
156
|
- [状態の一部を便利に扱うカスタムフック](#状態の一部を便利に扱うカスタムフック)
|
|
132
157
|
- [複雑な状態の形を使用](#複雑な状態の形を使用)
|
|
@@ -515,6 +540,123 @@ const tags = [
|
|
|
515
540
|
|
|
516
541
|
[サンプルコード](packages/example-react-router6/src/Form-for-test.tsx)
|
|
517
542
|
|
|
543
|
+
### Astro 向け useUrlState フック
|
|
544
|
+
|
|
545
|
+
React アイランド向けです。Astro にはデフォルトでクライアントサイドルーターがないため、このフックは `window.history` で URL を書き込み、戻る/進む操作時およびその他あらゆる `pushState`/`replaceState` の際(Astro 自身の `<ClientRouter />` を含む)に URL を読み戻します。ページ上のアイランドは状態を共有し、それらをラップするものは何も必要ありません。
|
|
546
|
+
|
|
547
|
+
[API ドキュメント](packages/urlstate/astro/useUrlState)
|
|
548
|
+
|
|
549
|
+
#### 例
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// src/state.ts
|
|
553
|
+
export const form: Form = {
|
|
554
|
+
name: '',
|
|
555
|
+
age: undefined,
|
|
556
|
+
agree_to_terms: false,
|
|
557
|
+
tags: [],
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
type Form = {
|
|
561
|
+
name: string;
|
|
562
|
+
age?: number;
|
|
563
|
+
agree_to_terms: boolean;
|
|
564
|
+
tags: { id: string; value: { text: string; time: Date } }[];
|
|
565
|
+
};
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
ページはオンデマンドでレンダリングされる必要があります(`output: 'server'`、またはページ側で `export const prerender = false` を指定し、アダプターを併用)。プリレンダリングされたページにはリクエストがないため、アイランドは `{}` を受け取り、ハイドレーション後に URL を読み取ります。
|
|
569
|
+
|
|
570
|
+
```astro
|
|
571
|
+
---
|
|
572
|
+
// src/pages/index.astro
|
|
573
|
+
import { Form } from '../components/Form';
|
|
574
|
+
import { Status } from '../components/Status';
|
|
575
|
+
|
|
576
|
+
// サーバーレンダリングが URL と一致するため、ハイドレーションで修正すべきものはありません。
|
|
577
|
+
// プレーンなオブジェクトにします。アイランドの props はシリアライズされますが、URLSearchParams はされません。
|
|
578
|
+
const searchParams = Object.fromEntries(Astro.url.searchParams);
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
<Form client:load searchParams={searchParams} />
|
|
582
|
+
<Status client:load searchParams={searchParams} />
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
// src/components/Form.tsx
|
|
587
|
+
import React from 'react';
|
|
588
|
+
import { useUrlState } from 'state-in-url/astro';
|
|
589
|
+
|
|
590
|
+
import { form } from '../state';
|
|
591
|
+
|
|
592
|
+
export function Form({ searchParams }: { searchParams?: Record<string, string> }) {
|
|
593
|
+
const { urlState, setUrl, setState } = useUrlState(form, { searchParams });
|
|
594
|
+
|
|
595
|
+
const onChangeTags = React.useCallback(
|
|
596
|
+
(tag: (typeof tags)[number]) => {
|
|
597
|
+
setUrl((curr) => ({
|
|
598
|
+
...curr,
|
|
599
|
+
tags: curr.tags.find((t) => t.id === tag.id)
|
|
600
|
+
? curr.tags.filter((t) => t.id !== tag.id)
|
|
601
|
+
: curr.tags.concat(tag),
|
|
602
|
+
}));
|
|
603
|
+
},
|
|
604
|
+
[setUrl],
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
return (
|
|
608
|
+
<div>
|
|
609
|
+
{tags.map((tag) => (
|
|
610
|
+
<Tag
|
|
611
|
+
active={!!urlState.tags.find((t) => t.id === tag.id)}
|
|
612
|
+
text={tag.value.text}
|
|
613
|
+
onClick={() => onChangeTags(tag)}
|
|
614
|
+
key={tag.id}
|
|
615
|
+
/>
|
|
616
|
+
))}
|
|
617
|
+
|
|
618
|
+
<input value={urlState.name}
|
|
619
|
+
onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
|
|
620
|
+
// 状態は即座に更新しつつ、変更は必要に応じて URL に同期できます
|
|
621
|
+
onBlur={() => setUrl()}
|
|
622
|
+
/>
|
|
623
|
+
</div>
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const tags = [
|
|
628
|
+
{ id: '1', value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') } },
|
|
629
|
+
{ id: '2', value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') } },
|
|
630
|
+
{ id: '3', value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') } },
|
|
631
|
+
];
|
|
632
|
+
|
|
633
|
+
// 2つ目のアイランド Status.tsx は同じ状態を読み取ります
|
|
634
|
+
export function Status({ searchParams }: { searchParams?: Record<string, string> }) {
|
|
635
|
+
const { urlState } = useUrlState(form, { searchParams });
|
|
636
|
+
return <pre>{JSON.stringify(urlState, null, 2)}</pre>;
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Preact アイランドも同じように動作します。`@astrojs/preact` を `compat: true` で使うと、サーバービルドとクライアントビルドの両方で `react` が `preact/compat` に解決され、上記のインポートはそのまま変わりません。
|
|
641
|
+
|
|
642
|
+
アイランドを使わず、クライアントフレームワークをまったく使わないページでは、同じ状態を [`decodeState` と `encodeState`](#encodestate-と-decodestate-ヘルパー) を通じてフロントマターに置けます:
|
|
643
|
+
|
|
644
|
+
```astro
|
|
645
|
+
---
|
|
646
|
+
import { decodeState, encodeState } from 'state-in-url/encodeState';
|
|
647
|
+
|
|
648
|
+
import { form } from '../state';
|
|
649
|
+
|
|
650
|
+
const state = decodeState(Astro.url.searchParams, form);
|
|
651
|
+
const withName = encodeState({ ...state, name: 'Alice' }, form, Astro.url.searchParams);
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
<pre>{JSON.stringify(state)}</pre>
|
|
655
|
+
<a href={`?${withName}`}>Alice</a>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
[サンプルコード](packages/example-astro/src/components/Form-for-test.tsx)、[素の Astro ページ](packages/example-astro/src/pages/pure-astro.astro)
|
|
659
|
+
|
|
518
660
|
## レシピ
|
|
519
661
|
##### 状態の一部を便利に扱うカスタムフック
|
|
520
662
|
<details>
|
|
@@ -787,7 +929,7 @@ export const useUserState = () => {
|
|
|
787
929
|
- [x] `react-router` 向けフック
|
|
788
930
|
- [x] `remix` 向けフック
|
|
789
931
|
- [ ] `svelte` 向けフック
|
|
790
|
-
- [
|
|
932
|
+
- [x] `astro` 向けフック
|
|
791
933
|
- [ ] ハッシュに状態を保存するフック?
|
|
792
934
|
|
|
793
935
|
## 連絡先とサポート
|