@asteby/metacore-runtime-react 25.1.0 → 25.1.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 25.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 7c19de1: fix(table): stop infinite reload loop when applying a column filter
8
+
9
+ The URL-resync effect read the location's search string from a render-time
10
+ snapshot, which lagged one commit behind the sibling write effect's imperative
11
+ `history.replaceState` (that write does not re-render). Applying any column
12
+ filter (e.g. the "Almacén" facet on /m/stock) made the resync misread its own
13
+ write as an external change, reset the filters, and ping-pong `{k:[v]} <-> {}`
14
+ every render — spinning the infinite-scroll fetch forever. The resync now reads
15
+ `window.location.search` fresh inside the effect, so a self-write is recognized
16
+ and skipped; only genuine external rewrites (sidebar deep-links, back/forward)
17
+ are adopted. Also lifts the pagination reset out of the `setDynamicFilters`
18
+ updater.
19
+
20
+ ## 25.1.1
21
+
22
+ ### Patch Changes
23
+
24
+ - dea9d02: fix(table): las listas en infinite-scroll recargan al bumpear refreshTrigger
25
+
26
+ El efecto de recarga de la tabla en modo infinite-scroll omitía `refreshTrigger` de sus deps (aunque el comentario decía que lo respetaba), así que tras crear/editar/eliminar en la página la lista infinita no se recargaba ("a veces no recarga la tabla"). Se añade `refreshTrigger` a las deps para igualar el path clásico.
27
+
3
28
  ## 25.1.0
4
29
 
5
30
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAiEnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,cAAsB,GACzB,EAAE,iBAAiB,+BAupCnB"}
1
+ {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAiEnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,cAAsB,GACzB,EAAE,iBAAiB,+BA6qCnB"}
@@ -314,16 +314,30 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
314
314
  // the table kept showing the previous filter. On every render where the
315
315
  // location's search differs from the last URL we ourselves wrote,
316
316
  // re-parse the `f_` params and adopt them.
317
- const locationSearch = typeof window !== 'undefined' ? window.location.search : '';
318
317
  useEffect(() => {
319
318
  if (!enableUrlSync || !initializedFromUrl.current)
320
319
  return;
320
+ // Read the URL FRESH inside the effect — NOT a value captured during
321
+ // render. The write effect above mutates the URL imperatively via
322
+ // `history.replaceState`, which does NOT trigger a re-render, so a search
323
+ // string snapshotted at render time lags one commit behind what we just
324
+ // wrote. Comparing that stale snapshot against `lastSelfSearch` (which the
325
+ // write effect updated in this same commit) always mismatched, so the
326
+ // resync "adopted" a phantom external change and reset the filters — the
327
+ // write effect then re-wrote them, and the two effects ping-ponged the
328
+ // filters `{almacen:['Test']} <-> {}` on every render, spinning the
329
+ // infinite-scroll fetch forever the instant any column filter was applied.
330
+ // Reading fresh here means that right after our own write, `now` already
331
+ // equals `lastSelfSearch` and we bail out early. Only a genuine external
332
+ // rewrite (router deep-link from a sidebar sibling, browser back/forward)
333
+ // makes `now` diverge, which is exactly when we want to adopt it.
334
+ const now = typeof window !== 'undefined' ? window.location.search : '';
321
335
  // Semantic compare: the router's spelling of the query string we just
322
336
  // wrote (different order/encoding) is NOT an external change.
323
- if (canonicalSearch(locationSearch) === canonicalSearch(lastSelfSearch.current ?? ''))
337
+ if (canonicalSearch(now) === canonicalSearch(lastSelfSearch.current ?? ''))
324
338
  return;
325
- lastSelfSearch.current = locationSearch;
326
- const params = new URLSearchParams(locationSearch);
339
+ lastSelfSearch.current = now;
340
+ const params = new URLSearchParams(now);
327
341
  const filters = {};
328
342
  params.forEach((rawValue, key) => {
329
343
  if (!key.startsWith('f_'))
@@ -337,14 +351,16 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
337
351
  else
338
352
  filters[filterKey] = [value];
339
353
  });
340
- setDynamicFilters((prev) => {
341
- if (JSON.stringify(prev) === JSON.stringify(filters))
342
- return prev;
354
+ // Compare against the current filters directly (the effect re-runs every
355
+ // render, so this closure is always fresh) and only touch state on a real
356
+ // change — resetting pagination OUTSIDE any updater, never nested inside
357
+ // another setState's updater.
358
+ if (JSON.stringify(dynamicFilters) !== JSON.stringify(filters)) {
359
+ setDynamicFilters(filters);
343
360
  setPagination((p) => ({ ...p, pageIndex: 0 }));
344
- return filters;
345
- });
361
+ }
346
362
  const search = params.get('search');
347
- if (search !== null)
363
+ if (search !== null && search !== globalFilter)
348
364
  setGlobalFilter(search);
349
365
  });
350
366
  const prefetchOptions = useCallback(async (endpoints) => {
@@ -669,8 +685,13 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
669
685
  void fetchAggregates();
670
686
  }, 300);
671
687
  return () => clearTimeout(timeoutId);
688
+ // refreshTrigger is included so an external bump (e.g. a create/edit/
689
+ // delete elsewhere on the page) reloads page 1 in infinite mode too —
690
+ // matching the classic path (fetchData carries refreshTrigger in its
691
+ // deps). Without it the comment above lied: infinite lists silently
692
+ // failed to reload after a create ("a veces no recarga la tabla").
672
693
  // eslint-disable-next-line react-hooks/exhaustive-deps
673
- }, [infiniteScroll, metadata, filterSignature]);
694
+ }, [infiniteScroll, metadata, filterSignature, refreshTrigger]);
674
695
  const handleRefresh = useCallback(() => {
675
696
  // Infinite mode owns its own list: refresh reloads page 1 and drops the
676
697
  // accumulated pages (a classic fetchData would collapse it to one small
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "25.1.0",
3
+ "version": "25.1.2",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -432,14 +432,28 @@ export function DynamicTable({
432
432
  // the table kept showing the previous filter. On every render where the
433
433
  // location's search differs from the last URL we ourselves wrote,
434
434
  // re-parse the `f_` params and adopt them.
435
- const locationSearch = typeof window !== 'undefined' ? window.location.search : ''
436
435
  useEffect(() => {
437
436
  if (!enableUrlSync || !initializedFromUrl.current) return
437
+ // Read the URL FRESH inside the effect — NOT a value captured during
438
+ // render. The write effect above mutates the URL imperatively via
439
+ // `history.replaceState`, which does NOT trigger a re-render, so a search
440
+ // string snapshotted at render time lags one commit behind what we just
441
+ // wrote. Comparing that stale snapshot against `lastSelfSearch` (which the
442
+ // write effect updated in this same commit) always mismatched, so the
443
+ // resync "adopted" a phantom external change and reset the filters — the
444
+ // write effect then re-wrote them, and the two effects ping-ponged the
445
+ // filters `{almacen:['Test']} <-> {}` on every render, spinning the
446
+ // infinite-scroll fetch forever the instant any column filter was applied.
447
+ // Reading fresh here means that right after our own write, `now` already
448
+ // equals `lastSelfSearch` and we bail out early. Only a genuine external
449
+ // rewrite (router deep-link from a sidebar sibling, browser back/forward)
450
+ // makes `now` diverge, which is exactly when we want to adopt it.
451
+ const now = typeof window !== 'undefined' ? window.location.search : ''
438
452
  // Semantic compare: the router's spelling of the query string we just
439
453
  // wrote (different order/encoding) is NOT an external change.
440
- if (canonicalSearch(locationSearch) === canonicalSearch(lastSelfSearch.current ?? '')) return
441
- lastSelfSearch.current = locationSearch
442
- const params = new URLSearchParams(locationSearch)
454
+ if (canonicalSearch(now) === canonicalSearch(lastSelfSearch.current ?? '')) return
455
+ lastSelfSearch.current = now
456
+ const params = new URLSearchParams(now)
443
457
  const filters: Record<string, string[]> = {}
444
458
  params.forEach((rawValue, key) => {
445
459
  if (!key.startsWith('f_')) return
@@ -449,13 +463,16 @@ export function DynamicTable({
449
463
  if (value.startsWith('IN:')) filters[filterKey] = value.substring(3).split(',')
450
464
  else filters[filterKey] = [value]
451
465
  })
452
- setDynamicFilters((prev: Record<string, string[]>) => {
453
- if (JSON.stringify(prev) === JSON.stringify(filters)) return prev
466
+ // Compare against the current filters directly (the effect re-runs every
467
+ // render, so this closure is always fresh) and only touch state on a real
468
+ // change — resetting pagination OUTSIDE any updater, never nested inside
469
+ // another setState's updater.
470
+ if (JSON.stringify(dynamicFilters) !== JSON.stringify(filters)) {
471
+ setDynamicFilters(filters)
454
472
  setPagination((p: PaginationState) => ({ ...p, pageIndex: 0 }))
455
- return filters
456
- })
473
+ }
457
474
  const search = params.get('search')
458
- if (search !== null) setGlobalFilter(search)
475
+ if (search !== null && search !== globalFilter) setGlobalFilter(search)
459
476
  })
460
477
 
461
478
  const prefetchOptions = useCallback(async (endpoints: string[]) => {
@@ -777,8 +794,13 @@ export function DynamicTable({
777
794
  void fetchAggregates()
778
795
  }, 300)
779
796
  return () => clearTimeout(timeoutId)
797
+ // refreshTrigger is included so an external bump (e.g. a create/edit/
798
+ // delete elsewhere on the page) reloads page 1 in infinite mode too —
799
+ // matching the classic path (fetchData carries refreshTrigger in its
800
+ // deps). Without it the comment above lied: infinite lists silently
801
+ // failed to reload after a create ("a veces no recarga la tabla").
780
802
  // eslint-disable-next-line react-hooks/exhaustive-deps
781
- }, [infiniteScroll, metadata, filterSignature])
803
+ }, [infiniteScroll, metadata, filterSignature, refreshTrigger])
782
804
 
783
805
  const handleRefresh = useCallback(() => {
784
806
  // Infinite mode owns its own list: refresh reloads page 1 and drops the