@lotics/app-sdk 0.21.0 → 0.22.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/dist/src/hooks.d.ts +16 -0
- package/dist/src/hooks.js +20 -12
- package/package.json +1 -1
package/dist/src/hooks.d.ts
CHANGED
|
@@ -2,7 +2,15 @@ import type { AppWorkflows, AppQueries } from "./types.js";
|
|
|
2
2
|
import type { ResolvedMember } from "./members.js";
|
|
3
3
|
interface QueryState<R> {
|
|
4
4
|
rows: R[];
|
|
5
|
+
/**
|
|
6
|
+
* True only on the initial load — a request is in flight and there are no
|
|
7
|
+
* rows yet. Stays false during background revalidation and while typing a new
|
|
8
|
+
* query (the previous rows remain visible), so consumers never blank data to a
|
|
9
|
+
* spinner on refetch. Use `isValidating` for a subtle refetch indicator.
|
|
10
|
+
*/
|
|
5
11
|
loading: boolean;
|
|
12
|
+
/** True whenever any request is in flight (initial load or revalidation). */
|
|
13
|
+
isValidating: boolean;
|
|
6
14
|
error: string | null;
|
|
7
15
|
/**
|
|
8
16
|
* Re-run the query from the first page. Use after a known mutation point —
|
|
@@ -37,6 +45,14 @@ export interface QueryOptions {
|
|
|
37
45
|
* whole table on first paint. Default `true`.
|
|
38
46
|
*/
|
|
39
47
|
enabled?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* When `false`, the query does not auto-refetch on window focus / tab return /
|
|
50
|
+
* network reconnect (`refetch()` still works). Default `true`, right for
|
|
51
|
+
* dashboards that should stay fresh. Set `false` for transient queries — a
|
|
52
|
+
* search bound to an ephemeral term, or on-demand detail — where a refocus
|
|
53
|
+
* re-run is wasted work and a visible reload.
|
|
54
|
+
*/
|
|
55
|
+
revalidateOnFocus?: boolean;
|
|
40
56
|
}
|
|
41
57
|
/**
|
|
42
58
|
* Trigger a workflow by alias from the app's manifest.
|
package/dist/src/hooks.js
CHANGED
|
@@ -36,6 +36,7 @@ export function useWorkflow(alias) {
|
|
|
36
36
|
export function useQuery(alias, params, opts) {
|
|
37
37
|
const pageSize = opts?.pageSize;
|
|
38
38
|
const enabled = opts?.enabled ?? true;
|
|
39
|
+
const revalidateOnFocus = opts?.revalidateOnFocus ?? true;
|
|
39
40
|
// Stringified params key — structurally-equal-but-new param objects don't
|
|
40
41
|
// re-fire the effect every render.
|
|
41
42
|
const paramsKey = JSON.stringify(params ?? {});
|
|
@@ -48,9 +49,9 @@ export function useQuery(alias, params, opts) {
|
|
|
48
49
|
const [state, setState] = useState(() => {
|
|
49
50
|
const mockRows = getMockRows(alias);
|
|
50
51
|
if (mockRows)
|
|
51
|
-
return { rows: mockRows,
|
|
52
|
-
// A disabled query starts idle
|
|
53
|
-
return { rows: [],
|
|
52
|
+
return { rows: mockRows, isValidating: false, error: null, hasMore: false };
|
|
53
|
+
// A disabled query starts idle; an enabled one is validating its first page.
|
|
54
|
+
return { rows: [], isValidating: enabled, error: null, hasMore: false };
|
|
54
55
|
});
|
|
55
56
|
const [loadingMore, setLoadingMore] = useState(false);
|
|
56
57
|
useEffect(() => {
|
|
@@ -58,15 +59,17 @@ export function useQuery(alias, params, opts) {
|
|
|
58
59
|
// without a hard reload. Fixture short-circuits the RPC.
|
|
59
60
|
const mockRows = getMockRows(alias);
|
|
60
61
|
if (mockRows) {
|
|
61
|
-
setState({ rows: mockRows,
|
|
62
|
+
setState({ rows: mockRows, isValidating: false, error: null, hasMore: false });
|
|
62
63
|
return;
|
|
63
64
|
}
|
|
64
65
|
if (!enabled) {
|
|
65
|
-
setState({ rows: [],
|
|
66
|
+
setState({ rows: [], isValidating: false, error: null, hasMore: false });
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
69
|
let cancelled = false;
|
|
69
|
-
|
|
70
|
+
// Keep prior rows in flight (stale-while-revalidate) — `loading` only shows
|
|
71
|
+
// when there's nothing to show yet, so refetches and keystrokes don't blank.
|
|
72
|
+
setState((s) => ({ ...s, isValidating: true, error: null }));
|
|
70
73
|
rpc("query", {
|
|
71
74
|
alias,
|
|
72
75
|
params: params ?? {},
|
|
@@ -79,7 +82,7 @@ export function useQuery(alias, params, opts) {
|
|
|
79
82
|
const rows = result.rows ?? [];
|
|
80
83
|
setState({
|
|
81
84
|
rows,
|
|
82
|
-
|
|
85
|
+
isValidating: false,
|
|
83
86
|
error: null,
|
|
84
87
|
hasMore: pageSize != null && rows.length === pageSize,
|
|
85
88
|
});
|
|
@@ -87,7 +90,8 @@ export function useQuery(alias, params, opts) {
|
|
|
87
90
|
.catch((err) => {
|
|
88
91
|
if (cancelled)
|
|
89
92
|
return;
|
|
90
|
-
|
|
93
|
+
// Keep the last good rows on a failed revalidation; surface the error.
|
|
94
|
+
setState((s) => ({ ...s, isValidating: false, error: err.message }));
|
|
91
95
|
});
|
|
92
96
|
return () => {
|
|
93
97
|
cancelled = true;
|
|
@@ -103,6 +107,8 @@ export function useQuery(alias, params, opts) {
|
|
|
103
107
|
// an app calls after a `useWorkflow()` mutation. Throttled so a tab return
|
|
104
108
|
// that fires both `focus` and `visibilitychange` only refetches once.
|
|
105
109
|
useEffect(() => {
|
|
110
|
+
if (!revalidateOnFocus)
|
|
111
|
+
return;
|
|
106
112
|
let last = 0;
|
|
107
113
|
const revalidate = () => {
|
|
108
114
|
if (document.visibilityState === "hidden")
|
|
@@ -121,9 +127,9 @@ export function useQuery(alias, params, opts) {
|
|
|
121
127
|
window.removeEventListener("online", revalidate);
|
|
122
128
|
document.removeEventListener("visibilitychange", revalidate);
|
|
123
129
|
};
|
|
124
|
-
}, []);
|
|
130
|
+
}, [revalidateOnFocus]);
|
|
125
131
|
const loadMore = useCallback(() => {
|
|
126
|
-
if (pageSize == null || loadingMore || state.
|
|
132
|
+
if (pageSize == null || loadingMore || state.isValidating || !state.hasMore)
|
|
127
133
|
return;
|
|
128
134
|
setLoadingMore(true);
|
|
129
135
|
rpc("query", {
|
|
@@ -148,8 +154,10 @@ export function useQuery(alias, params, opts) {
|
|
|
148
154
|
})
|
|
149
155
|
.finally(() => setLoadingMore(false));
|
|
150
156
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
151
|
-
}, [alias, paramsKey, pageSize, loadingMore, state.
|
|
152
|
-
|
|
157
|
+
}, [alias, paramsKey, pageSize, loadingMore, state.isValidating, state.hasMore, state.rows.length]);
|
|
158
|
+
// `loading` = initial load only (validating with nothing to show yet).
|
|
159
|
+
const loading = state.isValidating && state.rows.length === 0;
|
|
160
|
+
return { ...state, loading, refetch, loadMore, loadingMore };
|
|
153
161
|
}
|
|
154
162
|
/**
|
|
155
163
|
* Upload files from an app. The bytes are stored via a presigned
|