@buoy-gg/react-query 5.0.0 → 6.0.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.
@@ -199,7 +199,11 @@ function QueryBrowserModal({
199
199
  onModeChange: handleModeChange,
200
200
  enablePersistence: true,
201
201
  initialMode: "bottomSheet",
202
- enableGlitchEffects: true,
202
+ enableGlitchEffects: true
203
+ // The query list virtualizes itself (see QueryBrowser) — it must own
204
+ // scrolling. The filter view keeps the wrapper.
205
+ ,
206
+ disableScrollWrapper: !showFilterView,
203
207
  styles: {},
204
208
  footer: !showFilterView ? footerNode : undefined,
205
209
  footerHeight: !showFilterView ? 56 : undefined,
@@ -106,16 +106,51 @@ function QueryBrowser({
106
106
  })
107
107
  });
108
108
  }
109
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
110
- style: styles.listWrapper,
111
- contentContainerStyle: contentContainerStyle || styles.listContent,
112
- showsVerticalScrollIndicator: true,
113
- children: filteredQueries.map(query => /*#__PURE__*/(0, _jsxRuntime.jsx)(_QueryRow.default, {
114
- query: query,
115
- isSelected: selectedQuery?.queryHash === query.queryHash,
116
- onSelect: handleQuerySelect
117
- }, `${query.queryHash}-${query.state.dataUpdatedAt}-${query.state.fetchStatus}`))
118
- });
109
+ return (
110
+ /*#__PURE__*/
111
+ // Virtualized list with STABLE keys (queryHash). The previous
112
+ // ScrollView + map mounted every row, and its key included
113
+ // dataUpdatedAt/fetchStatus so rows REMOUNTED on each update — at 300
114
+ // seeded queries that measured 168% CPU with 546 row renders during a
115
+ // 5s idle dwell (tool-bench 2026-07-06). TanStack Query objects are
116
+ // mutable with stable identity, so `stateSig` carries the changing
117
+ // fields for the row's memo comparison.
118
+ (0, _jsxRuntime.jsx)(_reactNative.FlatList, {
119
+ style: styles.listWrapper,
120
+ contentContainerStyle: contentContainerStyle || styles.listContent,
121
+ data: filteredQueries,
122
+ keyExtractor: queryKeyExtractor,
123
+ renderItem: ({
124
+ item: query
125
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_QueryRow.default, {
126
+ query: query,
127
+ stateSig: computeQueryStateSig(query),
128
+ isSelected: selectedQuery?.queryHash === query.queryHash,
129
+ onSelect: handleQuerySelect
130
+ }),
131
+ showsVerticalScrollIndicator: true,
132
+ removeClippedSubviews: true,
133
+ initialNumToRender: 12,
134
+ maxToRenderPerBatch: 8,
135
+ windowSize: 5,
136
+ updateCellsBatchingPeriod: 50
137
+ })
138
+ );
139
+ }
140
+ const queryKeyExtractor = q => q.queryHash;
141
+
142
+ /** The row-visible slice of a query's mutable state, as a comparable
143
+ * string — drives QueryRow's memo (see comment above). */
144
+ function computeQueryStateSig(q) {
145
+ let observers = 0;
146
+ let stale = false;
147
+ try {
148
+ observers = q.getObserversCount();
149
+ stale = q.isStale();
150
+ } catch {
151
+ // defensive: state reads on a disposed query must not break the list
152
+ }
153
+ return `${q.state.dataUpdatedAt}|${q.state.fetchStatus}|${q.state.status}|${observers}|${stale}`;
119
154
  }
120
155
  const styles = _reactNative.StyleSheet.create({
121
156
  listWrapper: {
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
+ var _react = require("react");
7
8
  var _getQueryStatusLabel = require("../../utils/getQueryStatusLabel");
8
9
  var _sharedUi = require("@buoy-gg/shared-ui");
9
10
  var _jsxRuntime = require("react/jsx-runtime");
@@ -15,11 +16,15 @@ const getQueryText = query => {
15
16
  /**
16
17
  * Single row representation of a query for the list view, showing status and observer count.
17
18
  */
18
- const QueryRow = ({
19
+ // Memo justified by profiling (tool-bench 2026-07-06): 546 row renders in
20
+ // a 5s idle dwell at 300 queries. `stateSig` exists ONLY for this memo —
21
+ // Query objects are mutable with stable identity, so shallow-comparing
22
+ // `query` alone would never re-render (see QueryBrowser).
23
+ const QueryRow = /*#__PURE__*/(0, _react.memo)(function QueryRow({
19
24
  query,
20
25
  isSelected,
21
26
  onSelect
22
- }) => {
27
+ }) {
23
28
  // Buoy theme status color mapping
24
29
  const getStatusHexColor = status => {
25
30
  switch (status) {
@@ -76,5 +81,5 @@ const QueryRow = ({
76
81
  isSelected: isSelected,
77
82
  onPress: () => onSelect(query)
78
83
  });
79
- };
84
+ });
80
85
  var _default = exports.default = QueryRow;
@@ -195,7 +195,11 @@ export function QueryBrowserModal({
195
195
  onModeChange: handleModeChange,
196
196
  enablePersistence: true,
197
197
  initialMode: "bottomSheet",
198
- enableGlitchEffects: true,
198
+ enableGlitchEffects: true
199
+ // The query list virtualizes itself (see QueryBrowser) — it must own
200
+ // scrolling. The filter view keeps the wrapper.
201
+ ,
202
+ disableScrollWrapper: !showFilterView,
199
203
  styles: {},
200
204
  footer: !showFilterView ? footerNode : undefined,
201
205
  footerHeight: !showFilterView ? 56 : undefined,
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import { useMemo, useCallback } from "react";
4
- import { View, StyleSheet, Text, ScrollView } from "react-native";
4
+ import { View, StyleSheet, Text, FlatList } from "react-native";
5
5
  import QueryRow from "./QueryRow";
6
6
  import useAllQueries from "../../hooks/useAllQueries";
7
7
  import { getQueryStatusLabel } from "../../utils/getQueryStatusLabel";
@@ -101,16 +101,51 @@ export default function QueryBrowser({
101
101
  })
102
102
  });
103
103
  }
104
- return /*#__PURE__*/_jsx(ScrollView, {
105
- style: styles.listWrapper,
106
- contentContainerStyle: contentContainerStyle || styles.listContent,
107
- showsVerticalScrollIndicator: true,
108
- children: filteredQueries.map(query => /*#__PURE__*/_jsx(QueryRow, {
109
- query: query,
110
- isSelected: selectedQuery?.queryHash === query.queryHash,
111
- onSelect: handleQuerySelect
112
- }, `${query.queryHash}-${query.state.dataUpdatedAt}-${query.state.fetchStatus}`))
113
- });
104
+ return (
105
+ /*#__PURE__*/
106
+ // Virtualized list with STABLE keys (queryHash). The previous
107
+ // ScrollView + map mounted every row, and its key included
108
+ // dataUpdatedAt/fetchStatus so rows REMOUNTED on each update — at 300
109
+ // seeded queries that measured 168% CPU with 546 row renders during a
110
+ // 5s idle dwell (tool-bench 2026-07-06). TanStack Query objects are
111
+ // mutable with stable identity, so `stateSig` carries the changing
112
+ // fields for the row's memo comparison.
113
+ _jsx(FlatList, {
114
+ style: styles.listWrapper,
115
+ contentContainerStyle: contentContainerStyle || styles.listContent,
116
+ data: filteredQueries,
117
+ keyExtractor: queryKeyExtractor,
118
+ renderItem: ({
119
+ item: query
120
+ }) => /*#__PURE__*/_jsx(QueryRow, {
121
+ query: query,
122
+ stateSig: computeQueryStateSig(query),
123
+ isSelected: selectedQuery?.queryHash === query.queryHash,
124
+ onSelect: handleQuerySelect
125
+ }),
126
+ showsVerticalScrollIndicator: true,
127
+ removeClippedSubviews: true,
128
+ initialNumToRender: 12,
129
+ maxToRenderPerBatch: 8,
130
+ windowSize: 5,
131
+ updateCellsBatchingPeriod: 50
132
+ })
133
+ );
134
+ }
135
+ const queryKeyExtractor = q => q.queryHash;
136
+
137
+ /** The row-visible slice of a query's mutable state, as a comparable
138
+ * string — drives QueryRow's memo (see comment above). */
139
+ function computeQueryStateSig(q) {
140
+ let observers = 0;
141
+ let stale = false;
142
+ try {
143
+ observers = q.getObserversCount();
144
+ stale = q.isStale();
145
+ } catch {
146
+ // defensive: state reads on a disposed query must not break the list
147
+ }
148
+ return `${q.state.dataUpdatedAt}|${q.state.fetchStatus}|${q.state.status}|${observers}|${stale}`;
114
149
  }
115
150
  const styles = StyleSheet.create({
116
151
  listWrapper: {
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { memo } from "react";
3
4
  import { getQueryStatusLabel } from "../../utils/getQueryStatusLabel";
4
5
  import { CompactRow, buoyColors, useRelativeTime } from "@buoy-gg/shared-ui";
5
6
  import { jsx as _jsx } from "react/jsx-runtime";
@@ -11,11 +12,15 @@ const getQueryText = query => {
11
12
  /**
12
13
  * Single row representation of a query for the list view, showing status and observer count.
13
14
  */
14
- const QueryRow = ({
15
+ // Memo justified by profiling (tool-bench 2026-07-06): 546 row renders in
16
+ // a 5s idle dwell at 300 queries. `stateSig` exists ONLY for this memo —
17
+ // Query objects are mutable with stable identity, so shallow-comparing
18
+ // `query` alone would never re-render (see QueryBrowser).
19
+ const QueryRow = /*#__PURE__*/memo(function QueryRow({
15
20
  query,
16
21
  isSelected,
17
22
  onSelect
18
- }) => {
23
+ }) {
19
24
  // Buoy theme status color mapping
20
25
  const getStatusHexColor = status => {
21
26
  switch (status) {
@@ -72,5 +77,5 @@ const QueryRow = ({
72
77
  isSelected: isSelected,
73
78
  onPress: () => onSelect(query)
74
79
  });
75
- };
80
+ });
76
81
  export default QueryRow;
@@ -1 +1 @@
1
- {"version":3,"file":"QueryBrowserModal.d.ts","sourceRoot":"","sources":["../../../../../src/react-query/components/modals/QueryBrowserModal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAkB,MAAM,uBAAuB,CAAC;AAiBxE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,CAAC,EAAE,QAAQ,CAAC;IAC5B,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,KAAK,IAAI,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACjD,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,WAAW,EAAE,CAAC,GAAG,EAAE,SAAS,GAAG,WAAW,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,gBAAgB,EAChB,aAAa,EACb,OAAO,EACP,UAAU,EACV,YAAY,EAAE,oBAAoB,EAClC,cAAc,EAAE,sBAAsB,EACtC,2BAAmC,EACnC,WAAW,EACX,UAAe,EACf,cAAc,GACf,EAAE,sBAAsB,sCA4NxB"}
1
+ {"version":3,"file":"QueryBrowserModal.d.ts","sourceRoot":"","sources":["../../../../../src/react-query/components/modals/QueryBrowserModal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAkB,MAAM,uBAAuB,CAAC;AAiBxE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,CAAC,EAAE,QAAQ,CAAC;IAC5B,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,KAAK,IAAI,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACjD,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,WAAW,EAAE,CAAC,GAAG,EAAE,SAAS,GAAG,WAAW,KAAK,IAAI,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,gBAAgB,EAChB,aAAa,EACb,OAAO,EACP,UAAU,EACV,YAAY,EAAE,oBAAoB,EAClC,cAAc,EAAE,sBAAsB,EACtC,2BAAmC,EACnC,WAAW,EACX,UAAe,EACf,cAAc,GACf,EAAE,sBAAsB,sCA+NxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"QueryBrowser.d.ts","sourceRoot":"","sources":["../../../../../src/react-query/components/query-browser/QueryBrowser.tsx"],"names":[],"mappings":"AACA,OAAO,EAAsC,SAAS,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAM9C,UAAU,KAAK;IACb,aAAa,EAAE,KAAK,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,KAAK,IAAI,CAAC;IAClD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qBAAqB,CAAC,EAAE,SAAS,CAAC;IAClC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,aAAa,EACb,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,qBAAqB,EACrB,OAAO,EAAE,eAAe,EACxB,UAAe,EACf,eAA2B,EAC3B,gBAA4B,GAC7B,EAAE,KAAK,+BAiIP"}
1
+ {"version":3,"file":"QueryBrowser.d.ts","sourceRoot":"","sources":["../../../../../src/react-query/components/query-browser/QueryBrowser.tsx"],"names":[],"mappings":"AACA,OAAO,EAAoC,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAM9C,UAAU,KAAK;IACb,aAAa,EAAE,KAAK,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,KAAK,IAAI,CAAC;IAClD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qBAAqB,CAAC,EAAE,SAAS,CAAC;IAClC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,aAAa,EACb,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,qBAAqB,EACrB,OAAO,EAAE,eAAe,EACxB,UAAe,EACf,eAA2B,EAC3B,gBAA4B,GAC7B,EAAE,KAAK,+BA8IP"}
@@ -1,12 +1,14 @@
1
1
  import { Query } from "@tanstack/react-query";
2
2
  interface QueryRowProps {
3
3
  query: Query;
4
+ /** Changing state slice for the memo comparison — not read directly. */
5
+ stateSig: string;
4
6
  isSelected: boolean;
5
7
  onSelect: (query: Query) => void;
6
8
  }
7
9
  /**
8
10
  * Single row representation of a query for the list view, showing status and observer count.
9
11
  */
10
- declare const QueryRow: React.FC<QueryRowProps>;
12
+ declare const QueryRow: import("react").NamedExoticComponent<QueryRowProps>;
11
13
  export default QueryRow;
12
14
  //# sourceMappingURL=QueryRow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"QueryRow.d.ts","sourceRoot":"","sources":["../../../../../src/react-query/components/query-browser/QueryRow.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAiB9C,UAAU,aAAa;IACrB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,QAAA,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CA6DrC,CAAC;AAEF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"QueryRow.d.ts","sourceRoot":"","sources":["../../../../../src/react-query/components/query-browser/QueryRow.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAiB9C,UAAU,aAAa;IACrB,KAAK,EAAE,KAAK,CAAC;IACb,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;GAEG;AAKH,QAAA,MAAM,QAAQ,qDA6DZ,CAAC;AAEH,eAAe,QAAQ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/react-query",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "react-query package",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -26,8 +26,8 @@
26
26
  ],
27
27
  "sideEffects": false,
28
28
  "dependencies": {
29
- "@buoy-gg/shared-ui": "5.0.0",
30
- "@buoy-gg/floating-tools-core": "5.0.0"
29
+ "@buoy-gg/shared-ui": "6.0.0",
30
+ "@buoy-gg/floating-tools-core": "6.0.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@tanstack/react-query": ">=5.0.0",