@yoltra/react 0.6.0 → 0.8.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 CHANGED
@@ -3,22 +3,24 @@
3
3
  # @yoltra/react
4
4
 
5
5
  > 👉 🇲🇽 Versión en Español  |
6
- >  [ 🇺🇸 English Version](./README.md) 
6
+ >  [ 🇺🇸 English Versión](./README.md) 
7
7
 
8
- ![npm downloads](https://badgen.net/npm/dm/@yoltra/react)
9
- ![License](https://badgen.net/npm/license/@yoltra/react)
8
+ [![versión npm](https://img.shields.io/npm/v/@yoltra/react)](https://www.npmjs.com/package/@yoltra/react)
9
+ [![descargas npm](https://img.shields.io/npm/dm/@yoltra/react)](https://www.npmjs.com/package/@yoltra/react)
10
+ [![tipos](https://img.shields.io/npm/types/@yoltra/react)](https://www.npmjs.com/package/@yoltra/react)
11
+ [![Licencia](https://img.shields.io/npm/l/@yoltra/react)](https://github.com/yoltra/yoltra/blob/main/LICENSE)
10
12
 
11
13
  **Hooks de React para [yoltra](../../README.md) con
12
14
  suscripciones de grano fino por ruta.**
13
15
 
14
- Suscribete a `"items.0.title"` o `"items.*.done"` -- el componente se re-renderiza solo cuando
15
- esa ruta exacta cambia. Sin selectores, sin memoizacion, sin optimizacion manual.
16
+ Suscríbete a `"items.0.title"` o `"items.*.done"`. El componente se re-renderiza solo cuando
17
+ esa ruta exacta cambia. Sin selectores, sin memoización, sin optimización manual.
16
18
 
17
- [Ver la comparacion de flamegraph (Redux vs yoltra).](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-react/redux-yoltra-profiler.md)
19
+ [Ver la comparación de flamegraph (Redux vs yoltra).](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-react/redux-yoltra-profiler.md)
18
20
 
19
21
  ---
20
22
 
21
- ## Instalacion
23
+ ## Instalación
22
24
 
23
25
  ```bash
24
26
  npm install @yoltra/core @yoltra/react
@@ -28,11 +30,11 @@ npm install @yoltra/core @yoltra/react
28
30
 
29
31
  ---
30
32
 
31
- ## Configuracion con `createYoltra` (recomendado)
33
+ ## Configuración con `createYoltra` (recomendado)
32
34
 
33
- `createYoltra` crea el store **y** todos los hooks tipados en una sola llamada — sin archivo de
34
- context aparte, sin cableado de `createHooks`, sin provider obligatorio. Todos los parametros de
35
- tipo se infieren de tu reducer, asi que los componentes no necesitan generics explicitos.
35
+ `createYoltra` crea el store **y** todos los hooks tipados en una sola llamada, sin archivo de
36
+ context aparte, sin cableado de `createHooks`, sin provider obligatorio. Todos los parámetros de
37
+ tipo se infieren de tu reducer, así que los componentes no necesitan generics explícitos.
36
38
 
37
39
  ### 1. Crea el store y los hooks
38
40
 
@@ -74,10 +76,10 @@ export const { store, useAtomicProp, useEmit, StoreProvider } = createYoltra({
74
76
  });
75
77
  ```
76
78
 
77
- ### 2. Usa los hooks — sin provider
79
+ ### 2. Usa los hooks, sin provider
78
80
 
79
- Los hooks usan por defecto el store de arriba, asi que puedes renderizar componentes directamente.
80
- Suscribete con una spec **`{ reducer, property }`**: el `property` con puntos nombra la ruta exacta
81
+ Los hooks usan por defecto el store de arriba, así que puedes renderizar componentes directamente.
82
+ Suscríbete con una spec **`{ reducer, property }`**: el `property` con puntos nombra la ruta exacta
81
83
  a leer.
82
84
 
83
85
  ```tsx
@@ -85,7 +87,7 @@ a leer.
85
87
  import { useAtomicProp, useEmit } from "./yoltra";
86
88
 
87
89
  export function Counter() {
88
- // Forma objeto — se re-renderiza solo cuando counter.value cambia. Sin selectores, sin memo.
90
+ // Forma objeto: se re-renderiza solo cuando counter.value cambia. Sin selectores, sin memo.
89
91
  const value = useAtomicProp({ reducer: "counter", property: "value" });
90
92
  const emit = useEmit();
91
93
 
@@ -101,13 +103,13 @@ export function Counter() {
101
103
  ```
102
104
 
103
105
  Un `<StoreProvider>` solo se necesita para acotar una instancia **diferente** del store a un
104
- subarbol (p. ej. un store nuevo por test) — `createYoltra` devuelve uno justo para eso.
106
+ subarbol (p. ej. un store nuevo por test). `createYoltra` devuelve uno justo para eso.
105
107
 
106
108
  ---
107
109
 
108
110
  ## Avanzado: cableado manual con `createHooks`
109
111
 
110
- Cuando necesites un mismo conjunto de hooks compartido entre varias instancias de store a traves
112
+ Cuando necesites un mismo conjunto de hooks compartido entre varias instancias de store a través
111
113
  de tu propio context de React, vinculalos tu mismo con `createHooks(context)`. `createYoltra` es
112
114
  este mismo cableado colapsado en una sola llamada.
113
115
 
@@ -135,7 +137,41 @@ export const {
135
137
  } = createHooks(AppStoreContext);
136
138
  ```
137
139
 
138
- Provee el store con `<AppStoreContext.Provider value={store}>` en tu raiz.
140
+ Provee el store con `<AppStoreContext.Provider value={store}>` en tu raíz.
141
+
142
+ ---
143
+
144
+ ## Agregar a un store, con sus tipos
145
+
146
+ Una librería puede montar una slice en un store que no creó, y los hooks crecen para conocerla.
147
+ `withSlice`, `withMiddleware` y `withEffect` devuelven un `Yoltra` cuyos tipos se ampliaron.
148
+
149
+ ```tsx
150
+ import { defineSlice } from "@yoltra/core";
151
+
152
+ // Ámbito de módulo, una sola vez, antes del primer render.
153
+ export const app = createYoltra({ name: "App", reducer: { counter } })
154
+ .withSlice("transfers", defineSlice<TransferEM>()({ ... }));
155
+
156
+ export const { useAtomicProp, useEmit } = app;
157
+
158
+ // Tipado, sobre una slice que la aplicación nunca declaró.
159
+ const granted = useAtomicProp({ reducer: "transfers", property: "granted" });
160
+ ```
161
+
162
+ Tres cosas que conviene saber:
163
+
164
+ - **Ámbito de módulo, una vez, antes del primer render.** Cada llamada construye un conjunto
165
+ nuevo de hooks, porque `createHooks` asigna funciones nuevas. Llamarla dentro de un componente
166
+ le daría a React un `useAtomicProp` distinto en cada render.
167
+ - **El store y el contexto son los mismos objetos.** Solo cambian los tipos, así que un
168
+ `<StoreProvider>` de cualquier vista de la cadena sirve a los hooks de todas las demás, y la
169
+ caché de Suspense se comparte.
170
+ - **También existen las funciones libres**, para una librería que recibe un `Yoltra` que no creó:
171
+ `withSlice(yoltra, name, spec)`.
172
+
173
+ El contrato completo, incluido qué hacer con la disposición, está en la
174
+ [guía de decoración](https://github.com/yoltra/yoltra/blob/main/docs/es/DECORATION_GUIDE.md).
139
175
 
140
176
  ---
141
177
 
@@ -143,21 +179,21 @@ Provee el store con `<AppStoreContext.Provider value={store}>` en tu raiz.
143
179
 
144
180
  ### `useAtomicProp({ reducer, property }, map?, isEqual?)`
145
181
 
146
- Selector de ruta unica con grano fino. Se re-renderiza solo cuando la hoja especificada cambia. El
147
- `property` con puntos nombra la ruta exacta — incluyendo rutas dinamicas
182
+ Selector de ruta única con grano fino. Se re-renderiza solo cuando la hoja especificada cambia. El
183
+ `property` con puntos nombra la ruta exacta, incluyendo rutas dinámicas
148
184
  (`` `items.${id}.title` ``) y con comodines.
149
185
 
150
186
  ```tsx
151
- // Forma objeto (recomendada) — suscribete a la ruta exacta
187
+ // Forma objeto (recomendada): suscribete a la ruta exacta
152
188
  const title = useAtomicProp({ reducer: "todos", property: "items.0.title" });
153
189
 
154
- // Ruta dinamica — interpola la clave
190
+ // Ruta dinamica: interpola la clave
155
191
  const byId = useAtomicProp({ reducer: "todos", property: `items.${id}.title` });
156
192
 
157
- // Con mapper — derivar un valor de la ruta
193
+ // Con mapper: derivar un valor de la ruta
158
194
  const count = useAtomicProp({ reducer: "todos", property: "items" }, (items) => items.length);
159
195
 
160
- // Patron wildcard — se re-renderiza cuando cualquier item cambia
196
+ // Patron wildcard: se re-renderiza cuando cualquier item cambia
161
197
  const allTitles = useAtomicProp(
162
198
  { reducer: "todos", property: "items.**" },
163
199
  (state) => state.items.map((t) => t.title),
@@ -165,20 +201,20 @@ const allTitles = useAtomicProp(
165
201
  );
166
202
  ```
167
203
 
168
- > Tambien existe una sobrecarga con accessor tipado — `useAtomicProp("todos", (s) => s.items[0].title)`
169
- > — para rutas estaticas; autocompleta la forma del estado e infiere el tipo de retorno.
204
+ > También existe una sobrecarga con accessor tipado, `useAtomicProp("todos", (s) => s.items[0].title)`,
205
+ > para rutas estáticas; autocompleta la forma del estado e infiere el tipo de retorno.
170
206
 
171
207
  **Patrones soportados:**
172
208
 
173
- - `"items.0.title"` -- ruta exacta (incluyendo indices numericos de array)
174
- - `"items.*.title"` -- `*` coincide con un segmento
175
- - `"items.**"` -- `**` coincide con cero o mas segmentos
209
+ - `"items.0.title"`: ruta exacta (incluyendo índices numéricos de array)
210
+ - `"items.*.title"`: `*` coincide con un segmento
211
+ - `"items.**"`: `**` coincide con cero o más segmentos
176
212
 
177
213
  ---
178
214
 
179
215
  ### `useAtomicProps(specs, selector, isEqual?)`
180
216
 
181
- Selector de multiples rutas. Se suscribe a varias rutas y recalcula cuando alguna cambia.
217
+ Selector de múltiples rutas. Se suscribe a varias rutas y recalcula cuando alguna cambia.
182
218
 
183
219
  ```tsx
184
220
  const filtered = useAtomicProps(
@@ -193,18 +229,17 @@ const filtered = useAtomicProps(
193
229
 
194
230
  ---
195
231
 
196
- ### `useEvent(channel, type, handler, phase?)`
232
+ ### `useEvent(channel, type, handler, phase?, options?)`
197
233
 
198
- Suscribete a eventos del store desde un componente. No afecta el flujo de eventos --
199
- fire-and-forget.
234
+ Suscríbete a eventos del store desde un componente. No afecta el flujo de eventos. Es fire-and-forget.
200
235
 
201
236
  ```tsx
202
- // Eventos confirmados (por defecto) — eventos que pasaron el middleware
237
+ // Eventos confirmados (por defecto): eventos que pasaron el middleware
203
238
  useEvent("ui", "save", (event) => {
204
239
  showToast("Saved!");
205
240
  });
206
241
 
207
- // Eventos no confirmados — eventos rechazados por el middleware
242
+ // Eventos no confirmados: eventos rechazados por el middleware
208
243
  useEvent(
209
244
  "ui",
210
245
  "delete",
@@ -214,7 +249,7 @@ useEvent(
214
249
  "uncommitted",
215
250
  );
216
251
 
217
- // Todos los eventos — distinguir por fase
252
+ // Todos los eventos: distinguir por fase
218
253
  useEvent(
219
254
  "ui",
220
255
  "action",
@@ -227,15 +262,26 @@ useEvent(
227
262
 
228
263
  **Fases:**
229
264
 
230
- - `'committed'` (por defecto) -- eventos que pasaron el middleware y llegaron a los reducers
231
- - `'uncommitted'` -- eventos rechazados por el middleware
232
- - `'all'` -- ambos, con parametro `phase` para distinguir
265
+ - `'committed'` (por defecto): eventos que pasaron el middleware y llegaron a los reducers
266
+ - `'uncommitted'`: eventos rechazados por el middleware
267
+ - `'written'`: eventos que de verdad cambiaron el estado
268
+ - `'all'`: committed y uncommitted, con parámetro `phase` para distinguir
269
+
270
+ **Viaje en el tiempo.** Un handler **no** se ejecuta mientras DevTools está haciendo replay.
271
+ Recorrer una línea de tiempo volvía a ejecutar cada handler igual que un evento real, lo que
272
+ significaba volver a publicar, volver a escribir y volver a disparar analítica por eventos que no
273
+ estaban ocurriendo de nuevo. Actívalo solo para un handler que derive estado de vista del flujo
274
+ de eventos y no haga E/S:
275
+
276
+ ```tsx
277
+ useEvent("ui", "save", handler, "committed", { duringReplay: true });
278
+ ```
233
279
 
234
280
  ---
235
281
 
236
282
  ### `useEmit()`
237
283
 
238
- Retorna la funcion `emit` tipada del store (referencia estable).
284
+ Retorna la función `emit` tipada del store (referencia estable).
239
285
 
240
286
  ```tsx
241
287
  const emit = useEmit();
@@ -246,7 +292,7 @@ await emit("counter", "increment", 1);
246
292
 
247
293
  ### `useSelector(selector, isEqual?)`
248
294
 
249
- Selector de grano grueso via `useSyncExternalStore`. Se re-renderiza cuando el valor
295
+ Selector de grano grueso vía `useSyncExternalStore`. Se re-renderiza cuando el valor
250
296
  seleccionado cambia.
251
297
 
252
298
  ```tsx
@@ -270,7 +316,7 @@ const value = store.getState().counter.value;
270
316
  ```
271
317
 
272
318
  `getState()` es una lectura, no una suscripción. Llamado durante el render, el componente se
273
- renderiza una vez con ese valor y nunca más — nada le avisó de que el valor cambió. Parece que
319
+ renderiza una vez con ese valor y nunca más, porque nada le avisó de que el valor cambió. Parece que
274
320
  funciona hasta que el estado cambia y la pantalla no. Lee con `useAtomicProp` o `useSelector` lo
275
321
  que vayas a renderizar, y deja `getState()` para callbacks y efectos, que es para lo que es.
276
322
 
@@ -280,8 +326,8 @@ que vayas a renderizar, y deja `getState()` para callbacks y efectos, que es par
280
326
 
281
327
  ### `useSuspenseAtomicProp(spec, options)`
282
328
 
283
- Version compatible con Suspense de `useAtomicProp`. Lanza una promesa mientras carga, capturada
284
- por el boundary `<Suspense>` mas cercano.
329
+ Versión compatible con Suspense de `useAtomicProp`. Lanza una promesa mientras carga, capturada
330
+ por el boundary `<Suspense>` más cercano.
285
331
 
286
332
  ```tsx
287
333
  function UserName({ userId }: { userId: string }) {
@@ -303,7 +349,7 @@ function UserName({ userId }: { userId: string }) {
303
349
 
304
350
  ### `useSuspenseAtomicProps(specs, options)`
305
351
 
306
- Selector Suspense de multiples rutas.
352
+ Selector Suspense de múltiples rutas.
307
353
 
308
354
  ```tsx
309
355
  const stats = useSuspenseAtomicProps(
@@ -318,11 +364,11 @@ const stats = useSuspenseAtomicProps(
318
364
  ### Importalos de tu conjunto de hooks, no del barrel
319
365
 
320
366
  `createYoltra` y `createHooks` devuelven estos dos junto con el resto, ligados al mismo contexto.
321
- Deliberadamente **no** se exportan desde el barrel del paquete: una copia a nivel de paquete seria
322
- identica en forma y aun asi lanzaria `useStore must be used inside <StoreProvider>` en tiempo de
323
- ejecucion cuando el contexto que lee nunca se lleno — un error que los tipos no podian atrapar.
367
+ Deliberadamente **no** se exportan desde el barrel del paquete: una copia a nivel de paquete sería
368
+ idéntica en forma y aun así lanzaría `[yoltra] No store in context` en tiempo de
369
+ ejecución cuando el contexto que lee nunca se lleno, un error que los tipos no podian atrapar.
324
370
  Importarlos desde cualquier sitio que no sea el resultado de tu propio `createYoltra`/`createHooks`
325
- es ahora un error de compilacion, que es el mismo aviso llegando en el momento correcto.
371
+ es ahora un error de compilación, que es el mismo aviso llegando en el momento correcto.
326
372
 
327
373
  ```tsx
328
374
  // store.ts
@@ -332,8 +378,8 @@ export const { store, useAtomicProp, useSuspenseAtomicProp } = createYoltra({ ..
332
378
  import { useSuspenseAtomicProp } from "./store"; // ✅ conoce el store
333
379
  ```
334
380
 
335
- Los valores en cache tienen alcance por store, asi que dos stores que compartan nombre de reducer
336
- y ruta mantienen entradas separadas; las utilidades de invalidacion de abajo reciben una ruta y la
381
+ Los valores en cache tienen alcance por store, así que dos stores que compartan nombre de reducer
382
+ y ruta mantienen entradas separadas; las utilidades de invalidación de abajo reciben una ruta y la
337
383
  limpian en todos los stores que la hayan cacheado.
338
384
 
339
385
  ### Utilidades de cache
@@ -359,7 +405,7 @@ clearSuspenseCache();
359
405
 
360
406
  ## `shallowEqual`
361
407
 
362
- Comparador de igualdad superficial de objetos. Usalo como argumento `isEqual` cuando tu valor
408
+ Comparador de igualdad superficial de objetos. Úsalo como argumento `isEqual` cuando tu valor
363
409
  derivado es un objeto plano:
364
410
 
365
411
  ```tsx
@@ -372,7 +418,7 @@ const todos = useAtomicProp(
372
418
 
373
419
  ---
374
420
 
375
- ## Rendimiento: Antes y Despues
421
+ ## Rendimiento: Antes y Después
376
422
 
377
423
  ### Antes (grano grueso)
378
424
 
@@ -384,7 +430,7 @@ function TodoList() {
384
430
  }
385
431
  ```
386
432
 
387
- ### Despues (grano fino con yoltra)
433
+ ### Después (grano fino con yoltra)
388
434
 
389
435
  ```tsx
390
436
  // Cada TodoItem se re-renderiza SOLO cuando sus propios datos cambian
@@ -401,14 +447,14 @@ function TodoItem({ index }: { index: number }) {
401
447
  }
402
448
  ```
403
449
 
404
- [Ver la comparacion completa de flamegraph.](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-react/redux-yoltra-profiler.md)
450
+ [Ver la comparación completa de flamegraph.](https://github.com/yoltra/yoltra/blob/main/examples/v0/yoltra-in-react/redux-yoltra-profiler.md)
405
451
 
406
452
  ---
407
453
 
408
454
  ## Compatibilidad con React 18+
409
455
 
410
456
  - **Concurrent Mode:** Totalmente compatible. Todos los hooks usan `useSyncExternalStore`.
411
- - **Strict Mode:** La deduplicacion de eventos previene el doble procesamiento.
457
+ - **Strict Mode:** La deduplicación de eventos previene el doble procesamiento.
412
458
  - **Suspense:** `useSuspenseAtomicProp` y `useSuspenseAtomicProps` lanzan promesas para
413
459
  boundaries `<Suspense>`.
414
460
 
@@ -416,37 +462,37 @@ function TodoItem({ index }: { index: number }) {
416
462
 
417
463
  ## Ejemplos
418
464
 
419
- - **[App de Tareas con Profiler](../../examples/v0/yoltra-in-react)** -- CRUD completo con
420
- comparacion de flamegraph · [▶ Abrir la demo en vivo](https://yoltra.dev/es/demos/in-react)
421
- - **[Logo Cinetico (3000 particulas)](../../examples/v0/yoltra-kinetic-logo)** -- Suscripciones
465
+ - **[App de Tareas con Profiler](https://github.com/yoltra/yoltra/tree/main/examples/v0/yoltra-in-react)**: CRUD completo con
466
+ comparación de flamegraph · [▶ Abrir la demo en vivo](https://yoltra.dev/es/demos/in-react)
467
+ - **[Logo Cinético (3000 particulas)](https://github.com/yoltra/yoltra/tree/main/examples/v0/yoltra-kinetic-logo)**: Suscripciones
422
468
  independientes por circulo SVG · [▶ Abrir la demo en vivo](https://yoltra.dev/es/demos/kinetic-logo)
423
- - **[Next.js (Pages Router)](../../examples/v0/yoltra-in-nextjs)** -- estado de cliente + cambio de tema · [▶ Abrir la demo en vivo](https://yoltra.dev/es/demos/in-nextjs)
469
+ - **[Next.js (Pages Router)](https://github.com/yoltra/yoltra/tree/main/examples/v0/yoltra-in-nextjs)**: estado de cliente + cambio de tema · [▶ Abrir la demo en vivo](https://yoltra.dev/es/demos/in-nextjs)
424
470
 
425
471
  ---
426
472
 
427
- ## Documentacion
473
+ ## Documentación
428
474
 
429
- - **[README raiz de yoltra](../../README.md)** --
430
- Descripcion general y configuracion rapida
431
- - **[API de @yoltra/core](../core/README.md)**
432
- -- Store, middleware, efectos, matchers `When`
433
- - **[Guia de Inicio Rapido](https://github.com/yoltra/yoltra/blob/main/docs/en/QUICK_START_GUIDE.md)**
434
- -- Cinco pasos hacia una app funcional
435
- - **[Comparacion de Bibliotecas](https://github.com/yoltra/yoltra/blob/main/docs/en/design/state-management-library-comparison.md)**
436
- -- Comparacion arquitectonica
475
+ - **[README raíz de yoltra](../../README.md)**:
476
+ Descripción general y configuración rápida
477
+ - **[API de @yoltra/core](../core/README.md)**:
478
+ Store, middleware, efectos, matchers `When`
479
+ - **[Guia de Inicio Rápido](https://github.com/yoltra/yoltra/blob/main/docs/en/QUICK_START_GUIDE.md)**:
480
+ Cinco pasos hacia una app funcional
481
+ - **[Comparación de Bibliotecas](https://github.com/yoltra/yoltra/blob/main/docs/en/design/state-management-library-comparison.md)**:
482
+ Comparación arquitectónica
437
483
 
438
484
  ---
439
485
 
440
486
  ## Contribuir
441
487
 
442
- - [Raiz del Monorepo](../../)
443
- - [Guia de Contribucion](../../CONTRIBUTING.md)
488
+ - [Raíz del Monorepo](https://github.com/yoltra/yoltra)
489
+ - [Guia de Contribución](../../CONTRIBUTING.md)
444
490
 
445
491
  ---
446
492
 
447
493
  ## Estado
448
494
 
449
- **Release Candidate** -- Las APIs son estables, usadas en produccion, cambios menores posibles
495
+ **Release Candidate**. Las APIs son estables, usadas en producción, cambios menores posibles
450
496
  antes de v1.0.0.
451
497
 
452
498
  ---
@@ -454,7 +500,7 @@ antes de v1.0.0.
454
500
  ## Colecciones normalizadas
455
501
 
456
502
  `useEntityIds`, `useEntity` y `useEntityField` se emparejan con `createEntityAdapter` de
457
- `@yoltra/core`. Son envoltorios delgados sobre `useAtomicProp`; el valor esta en que la ruta viene
503
+ `@yoltra/core`. Son envoltorios delgados sobre `useAtomicProp`; el valor está en que la ruta viene
458
504
  del adapter en vez de escribirse a mano en un componente, donde nada la verifica.
459
505
 
460
506
  ```tsx
@@ -472,4 +518,4 @@ function Row({ id }: { id: string }) {
472
518
 
473
519
  ## Licencia
474
520
 
475
- **MIT** -- Libre para usar en proyectos comerciales y de codigo abierto.
521
+ **MIT**. Libre para usar en proyectos comerciales y de código abierto.