@colixsystems/widget-sdk 0.67.0 → 0.67.1

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.
Files changed (2) hide show
  1. package/dist/hooks.js +35 -0
  2. package/package.json +2 -2
package/dist/hooks.js CHANGED
@@ -593,6 +593,12 @@ function toDatastoreError(err) {
593
593
  });
594
594
  }
595
595
 
596
+ // sc-1579 — consecutive renders with a different serialized query before
597
+ // `useDatastoreQuery` calls the loop out. High enough that no real interaction
598
+ // reaches it (a settled fetch resets the count), low enough to fire instantly
599
+ // on a genuine loop.
600
+ const VOLATILE_QUERY_RENDER_LIMIT = 10;
601
+
596
602
  /**
597
603
  * Stateful datastore query hook. Returns { data, loading, error, refetch }.
598
604
  *
@@ -682,6 +688,35 @@ export function useDatastoreQuery(table, query) {
682
688
  return null;
683
689
  }
684
690
  })();
691
+
692
+ // sc-1579 — a query argument built from a volatile value (`Date.now()`,
693
+ // `new Date()`, `Math.random()`) serialises differently every render, so the
694
+ // effect below re-fetches forever and the widget never settles. A genuine
695
+ // edit (a typed filter) settles within one render because the re-render the
696
+ // fetch triggers reuses the same argument; a volatile one never does. That
697
+ // gap is the signal — no timers, no heuristics. Warned once per instance,
698
+ // and in production too: the Studio canvas and the widget agent's render
699
+ // smoke check both run production builds, which is where this must surface.
700
+ const volatileKeyRef = useRef({ key: queryKey, changes: 0, warned: false });
701
+ const keyWatch = volatileKeyRef.current;
702
+ if (keyWatch.key === queryKey) {
703
+ keyWatch.changes = 0;
704
+ } else {
705
+ keyWatch.key = queryKey;
706
+ keyWatch.changes += 1;
707
+ if (keyWatch.changes >= VOLATILE_QUERY_RENDER_LIMIT && !keyWatch.warned) {
708
+ keyWatch.warned = true;
709
+ // eslint-disable-next-line no-console
710
+ console.warn(
711
+ `useDatastoreQuery(${JSON.stringify(table)}): the query argument changed on ` +
712
+ `${keyWatch.changes} consecutive renders, so this widget is re-fetching in a ` +
713
+ "loop. Something in the query is regenerated every render — usually a date " +
714
+ "or random value. Compute it once outside render (or with useMemo) and pass " +
715
+ "the stable value in.",
716
+ );
717
+ }
718
+ }
719
+
685
720
  useEffect(() => {
686
721
  doFetch();
687
722
  // eslint-disable-next-line react-hooks/exhaustive-deps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.67.0",
3
+ "version": "0.67.1",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -48,7 +48,7 @@
48
48
  ],
49
49
  "scripts": {
50
50
  "build": "node scripts/build.js",
51
- "test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-filestore-upload.test.js src/__tests__/hooks-filestore-file.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-subscription.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/linter-image-height.test.js src/__tests__/linter-platform.test.js src/__tests__/linter-react-import.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/lucideIconName.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js src/__tests__/theme-components-parity.test.js"
51
+ "test": "node --test src/__tests__/contract.test.js src/__tests__/hooks-users.test.js src/__tests__/hooks-groups.test.js src/__tests__/hooks-schema.test.js src/__tests__/hooks-assets-by-tag.test.js src/__tests__/hooks-filestore-upload.test.js src/__tests__/hooks-filestore-file.test.js src/__tests__/hooks-mutation.test.js src/__tests__/hooks-record-permissions.test.js src/__tests__/hooks-geolocation.test.js src/__tests__/hooks-subscription.test.js src/__tests__/hooks-volatile-query-key.test.js src/__tests__/linter-users-scope.test.js src/__tests__/linter-comments.test.js src/__tests__/linter-image-height.test.js src/__tests__/linter-platform.test.js src/__tests__/linter-react-import.test.js src/__tests__/lucide-icon-names.test.js src/__tests__/lucideIconName.test.js src/__tests__/manifest-actions.test.js src/__tests__/widget-translations.test.js src/__tests__/devserver.test.js src/__tests__/host-externals.test.js src/__tests__/datetimepicker.test.js src/__tests__/property-schema-resolve.test.js src/__tests__/theme-components-parity.test.js"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=18"