@effector-tanstack-query/react 0.1.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.md +37 -0
- package/dist/index.cjs +203 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +130 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +177 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/src/index.ts +439 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @effector-tanstack-query/react
|
|
2
|
+
|
|
3
|
+
React hooks for [`@effector-tanstack-query/core`](https://www.npmjs.com/package/@effector-tanstack-query/core) — subscribe a component to a query/mutation with auto mount/unmount lifecycle.
|
|
4
|
+
|
|
5
|
+
- `useQuery(query)` — subscribes to query stores, returns `{ data, error, isFetching, refresh, … }`
|
|
6
|
+
- `useMutation(mutation)` — returns `{ data, mutate, mutateWith, reset, … }`
|
|
7
|
+
- `useInfiniteQuery(query)` — paginated variant
|
|
8
|
+
- `useSuspenseQuery(query)` / `useSuspenseInfiniteQuery(query)` — Suspense-friendly with non-nullable `data`
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @effector-tanstack-query/core @effector-tanstack-query/react \
|
|
12
|
+
@tanstack/query-core effector effector-react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { useQuery } from '@effector-tanstack-query/react'
|
|
17
|
+
|
|
18
|
+
function UserProfile() {
|
|
19
|
+
const { data, isPending, error, refresh } = useQuery(userQuery)
|
|
20
|
+
if (isPending) return <p>Loading…</p>
|
|
21
|
+
if (error) return <p>Error: {error.message}</p>
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<h1>{data.name}</h1>
|
|
25
|
+
<button onClick={refresh}>refresh</button>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Full documentation:** https://ilyaagarkov.github.io/effector-tanstack-query/
|
|
32
|
+
|
|
33
|
+
**Source & examples:** https://github.com/ilyaagarkov/effector-tanstack-query
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var effectorReact = require('effector-react');
|
|
5
|
+
|
|
6
|
+
function _interopNamespace(e) {
|
|
7
|
+
if (e && e.__esModule) return e;
|
|
8
|
+
var n = Object.create(null);
|
|
9
|
+
if (e) {
|
|
10
|
+
Object.keys(e).forEach(function (k) {
|
|
11
|
+
if (k !== 'default') {
|
|
12
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
13
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function () { return e[k]; }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
n.default = e;
|
|
21
|
+
return Object.freeze(n);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
function useQuery(query) {
|
|
28
|
+
const state = effectorReact.useUnit({
|
|
29
|
+
data: query.$data,
|
|
30
|
+
error: query.$error,
|
|
31
|
+
status: query.$status,
|
|
32
|
+
isPending: query.$isPending,
|
|
33
|
+
isFetching: query.$isFetching,
|
|
34
|
+
isSuccess: query.$isSuccess,
|
|
35
|
+
isError: query.$isError,
|
|
36
|
+
isPlaceholderData: query.$isPlaceholderData,
|
|
37
|
+
fetchStatus: query.$fetchStatus
|
|
38
|
+
});
|
|
39
|
+
const mount = effectorReact.useUnit(query.mounted);
|
|
40
|
+
const unmount = effectorReact.useUnit(query.unmounted);
|
|
41
|
+
const refresh = effectorReact.useUnit(query.refresh);
|
|
42
|
+
React__namespace.useEffect(() => {
|
|
43
|
+
mount();
|
|
44
|
+
return () => unmount();
|
|
45
|
+
}, [mount, unmount]);
|
|
46
|
+
return { ...state, refresh };
|
|
47
|
+
}
|
|
48
|
+
function useMutation(mutation) {
|
|
49
|
+
const state = effectorReact.useUnit({
|
|
50
|
+
data: mutation.$data,
|
|
51
|
+
error: mutation.$error,
|
|
52
|
+
status: mutation.$status,
|
|
53
|
+
variables: mutation.$variables,
|
|
54
|
+
isPaused: mutation.$isPaused,
|
|
55
|
+
isPending: mutation.$isPending,
|
|
56
|
+
isSuccess: mutation.$isSuccess,
|
|
57
|
+
isError: mutation.$isError,
|
|
58
|
+
isIdle: mutation.$isIdle
|
|
59
|
+
});
|
|
60
|
+
const start = effectorReact.useUnit(mutation.start);
|
|
61
|
+
const unmount = effectorReact.useUnit(mutation.unmounted);
|
|
62
|
+
const mutate = effectorReact.useUnit(mutation.mutate);
|
|
63
|
+
const mutateWith = effectorReact.useUnit(mutation.mutateWith);
|
|
64
|
+
const reset = effectorReact.useUnit(mutation.reset);
|
|
65
|
+
React__namespace.useEffect(() => {
|
|
66
|
+
start();
|
|
67
|
+
return () => unmount();
|
|
68
|
+
}, [start, unmount]);
|
|
69
|
+
return { ...state, mutate, mutateWith, reset };
|
|
70
|
+
}
|
|
71
|
+
function useInfiniteQuery(query) {
|
|
72
|
+
const state = effectorReact.useUnit({
|
|
73
|
+
data: query.$data,
|
|
74
|
+
error: query.$error,
|
|
75
|
+
status: query.$status,
|
|
76
|
+
isPending: query.$isPending,
|
|
77
|
+
isFetching: query.$isFetching,
|
|
78
|
+
isSuccess: query.$isSuccess,
|
|
79
|
+
isError: query.$isError,
|
|
80
|
+
isPlaceholderData: query.$isPlaceholderData,
|
|
81
|
+
fetchStatus: query.$fetchStatus,
|
|
82
|
+
hasNextPage: query.$hasNextPage,
|
|
83
|
+
hasPreviousPage: query.$hasPreviousPage,
|
|
84
|
+
isFetchingNextPage: query.$isFetchingNextPage,
|
|
85
|
+
isFetchingPreviousPage: query.$isFetchingPreviousPage,
|
|
86
|
+
isFetchNextPageError: query.$isFetchNextPageError,
|
|
87
|
+
isFetchPreviousPageError: query.$isFetchPreviousPageError
|
|
88
|
+
});
|
|
89
|
+
const mount = effectorReact.useUnit(query.mounted);
|
|
90
|
+
const unmount = effectorReact.useUnit(query.unmounted);
|
|
91
|
+
const refresh = effectorReact.useUnit(query.refresh);
|
|
92
|
+
const fetchNextPage = effectorReact.useUnit(query.fetchNextPage);
|
|
93
|
+
const fetchPreviousPage = effectorReact.useUnit(query.fetchPreviousPage);
|
|
94
|
+
React__namespace.useEffect(() => {
|
|
95
|
+
mount();
|
|
96
|
+
return () => unmount();
|
|
97
|
+
}, [mount, unmount]);
|
|
98
|
+
return { ...state, refresh, fetchNextPage, fetchPreviousPage };
|
|
99
|
+
}
|
|
100
|
+
function useObserverRerender(observer) {
|
|
101
|
+
const [, forceRender] = React__namespace.useReducer((x) => x + 1, 0);
|
|
102
|
+
React__namespace.useEffect(() => {
|
|
103
|
+
if (!observer) return;
|
|
104
|
+
return observer.subscribe(forceRender);
|
|
105
|
+
}, [observer]);
|
|
106
|
+
}
|
|
107
|
+
function useSuspenseQuery(query) {
|
|
108
|
+
const mount = effectorReact.useUnit(query.mounted);
|
|
109
|
+
const unmount = effectorReact.useUnit(query.unmounted);
|
|
110
|
+
const refresh = effectorReact.useUnit(query.refresh);
|
|
111
|
+
React__namespace.useEffect(() => {
|
|
112
|
+
mount();
|
|
113
|
+
return () => unmount();
|
|
114
|
+
}, [mount, unmount]);
|
|
115
|
+
const observer = useSuspenseObserver(query);
|
|
116
|
+
useObserverRerender(observer);
|
|
117
|
+
const result = observer.getOptimisticResult(observer.options);
|
|
118
|
+
if (result.status === "error") throw result.error;
|
|
119
|
+
if (result.status === "pending") {
|
|
120
|
+
throw observer.fetchOptimistic(observer.options);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
data: result.data,
|
|
124
|
+
error: result.error,
|
|
125
|
+
status: "success",
|
|
126
|
+
isPending: false,
|
|
127
|
+
isSuccess: true,
|
|
128
|
+
isError: false,
|
|
129
|
+
isFetching: result.isFetching,
|
|
130
|
+
isPlaceholderData: result.isPlaceholderData,
|
|
131
|
+
fetchStatus: result.fetchStatus,
|
|
132
|
+
refresh
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function useSuspenseInfiniteQuery(query) {
|
|
136
|
+
const mount = effectorReact.useUnit(query.mounted);
|
|
137
|
+
const unmount = effectorReact.useUnit(query.unmounted);
|
|
138
|
+
const refresh = effectorReact.useUnit(query.refresh);
|
|
139
|
+
const fetchNextPage = effectorReact.useUnit(query.fetchNextPage);
|
|
140
|
+
const fetchPreviousPage = effectorReact.useUnit(query.fetchPreviousPage);
|
|
141
|
+
React__namespace.useEffect(() => {
|
|
142
|
+
mount();
|
|
143
|
+
return () => unmount();
|
|
144
|
+
}, [mount, unmount]);
|
|
145
|
+
const observer = useSuspenseObserver(query);
|
|
146
|
+
useObserverRerender(observer);
|
|
147
|
+
const result = observer.getOptimisticResult(observer.options);
|
|
148
|
+
if (result.status === "error") throw result.error;
|
|
149
|
+
if (result.status === "pending") {
|
|
150
|
+
throw observer.fetchOptimistic(observer.options);
|
|
151
|
+
}
|
|
152
|
+
const r = result;
|
|
153
|
+
return {
|
|
154
|
+
data: r.data,
|
|
155
|
+
error: r.error,
|
|
156
|
+
status: "success",
|
|
157
|
+
isPending: false,
|
|
158
|
+
isSuccess: true,
|
|
159
|
+
isError: false,
|
|
160
|
+
isFetching: r.isFetching,
|
|
161
|
+
isPlaceholderData: r.isPlaceholderData,
|
|
162
|
+
fetchStatus: r.fetchStatus,
|
|
163
|
+
hasNextPage: r.hasNextPage,
|
|
164
|
+
hasPreviousPage: r.hasPreviousPage,
|
|
165
|
+
isFetchingNextPage: r.isFetchingNextPage,
|
|
166
|
+
isFetchingPreviousPage: r.isFetchingPreviousPage,
|
|
167
|
+
isFetchNextPageError: r.isFetchNextPageError,
|
|
168
|
+
isFetchPreviousPageError: r.isFetchPreviousPageError,
|
|
169
|
+
refresh,
|
|
170
|
+
fetchNextPage,
|
|
171
|
+
fetchPreviousPage
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function useSuspenseObserver(query) {
|
|
175
|
+
const factory = query;
|
|
176
|
+
const observerInScope = effectorReact.useUnit(query.$observer);
|
|
177
|
+
const qc = effectorReact.useUnit(query.$queryClient);
|
|
178
|
+
const queryKey = effectorReact.useUnit(factory.__resolvedKey);
|
|
179
|
+
const enabled = effectorReact.useUnit(factory.__enabled);
|
|
180
|
+
const transient = React__namespace.useMemo(() => {
|
|
181
|
+
if (observerInScope || !qc) return null;
|
|
182
|
+
return factory.__createObserver(qc, { queryKey, enabled });
|
|
183
|
+
}, [observerInScope, qc, factory]);
|
|
184
|
+
React__namespace.useEffect(() => {
|
|
185
|
+
if (!transient) return;
|
|
186
|
+
transient.setOptions({ ...transient.options, queryKey, enabled });
|
|
187
|
+
}, [transient, queryKey, enabled]);
|
|
188
|
+
const observer = observerInScope ?? transient;
|
|
189
|
+
if (!observer) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
"[@effector-tanstack-query/react] useSuspenseQuery: no QueryClient is set. Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] })."
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return observer;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
exports.useInfiniteQuery = useInfiniteQuery;
|
|
198
|
+
exports.useMutation = useMutation;
|
|
199
|
+
exports.useQuery = useQuery;
|
|
200
|
+
exports.useSuspenseInfiniteQuery = useSuspenseInfiniteQuery;
|
|
201
|
+
exports.useSuspenseQuery = useSuspenseQuery;
|
|
202
|
+
//# sourceMappingURL=index.cjs.map
|
|
203
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["useUnit","React"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as React from 'react'\nimport { useUnit } from 'effector-react'\nimport type {\n FetchStatus,\n MutateOptions,\n QueryStatus,\n} from '@tanstack/query-core'\nimport type {\n InfiniteQueryResult,\n MutationResult,\n MutationStatus,\n QueryResult,\n} from '@effector-tanstack-query/core'\n\nexport interface UseQueryResult<TData, TError = Error> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Subscribes a React component to a query, automatically calling\n * `mounted()` on mount and `unmounted()` on cleanup.\n */\nexport function useQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh }\n}\n\nexport interface UseMutationResult<TData, TError, TVariables> {\n data: TData | undefined\n error: TError | null\n status: MutationStatus\n variables: TVariables | undefined\n isPaused: boolean\n isPending: boolean\n isSuccess: boolean\n isError: boolean\n isIdle: boolean\n mutate: (variables: TVariables) => void\n /**\n * Trigger the mutation with per-call callbacks layered on top of the\n * observer-level ones (`onSuccess` / `onError` / `onSettled` in\n * `createMutation` options). Use this for component-local reactions\n * that don't fit module-level `sample` wiring — navigation after success,\n * one-shot toasts, etc.\n */\n mutateWith: (args: {\n variables: TVariables\n onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess']\n onError?: MutateOptions<TData, TError, TVariables>['onError']\n onSettled?: MutateOptions<TData, TError, TVariables>['onSettled']\n }) => void\n reset: () => void\n}\n\n/**\n * Subscribes a React component to a mutation, automatically calling\n * `start()` on mount and `unmounted()` on cleanup so the queryClient can\n * garbage-collect the mutation entry once no observers remain.\n */\nexport function useMutation<TData = unknown, TError = Error, TVariables = void>(\n mutation: MutationResult<TData, TError, TVariables>,\n): UseMutationResult<TData, TError, TVariables> {\n const state = useUnit({\n data: mutation.$data,\n error: mutation.$error,\n status: mutation.$status,\n variables: mutation.$variables,\n isPaused: mutation.$isPaused,\n isPending: mutation.$isPending,\n isSuccess: mutation.$isSuccess,\n isError: mutation.$isError,\n isIdle: mutation.$isIdle,\n })\n\n const start = useUnit(mutation.start)\n const unmount = useUnit(mutation.unmounted)\n const mutate = useUnit(mutation.mutate)\n const mutateWith = useUnit(mutation.mutateWith)\n const reset = useUnit(mutation.reset)\n\n React.useEffect(() => {\n start()\n return () => unmount()\n }, [start, unmount])\n\n return { ...state, mutate, mutateWith, reset }\n}\n\nexport interface UseInfiniteQueryResult<TData, TError> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Subscribes a React component to an infinite query, with auto mount/unmount\n * lifecycle and bound `fetchNextPage` / `fetchPreviousPage` callbacks.\n */\nexport function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseInfiniteQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n hasNextPage: query.$hasNextPage,\n hasPreviousPage: query.$hasPreviousPage,\n isFetchingNextPage: query.$isFetchingNextPage,\n isFetchingPreviousPage: query.$isFetchingPreviousPage,\n isFetchNextPageError: query.$isFetchNextPageError,\n isFetchPreviousPageError: query.$isFetchPreviousPageError,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh, fetchNextPage, fetchPreviousPage }\n}\n\n// Suspense data path: read from a per-scope observer.\n//\n// The scope mount chain runs in useEffect, which is skipped while a component\n// is suspended — so on the very first render the scope's `$observer` may be\n// null. To get synchronous access to the observer's promise during suspense,\n// we construct a transient observer via the factory's hidden\n// `__createObserver(qc, { queryKey, enabled })` helper. The transient observer\n// reads from / writes to the same queryClient cache as the eventual scope\n// observer (which is created when mountFx runs after useEffect commits).\n//\n// The mount/unmount effect is still wired up so that other consumers reading\n// the same query through `useUnit` / `useQuery` see updates in scope state.\n\nfunction useObserverRerender(\n observer: { subscribe: (cb: () => void) => () => void } | null,\n): void {\n const [, forceRender] = React.useReducer((x: number) => x + 1, 0)\n React.useEffect(() => {\n if (!observer) return\n return observer.subscribe(forceRender)\n }, [observer])\n}\n\ninterface SuspenseFactory<TObserver> {\n __createObserver(\n qc: import('@tanstack/query-core').QueryClient,\n init: { queryKey: unknown; enabled: boolean },\n ): TObserver\n __resolvedKey: import('effector').Store<unknown>\n __enabled: import('effector').Store<boolean>\n}\n\nexport interface UseSuspenseQueryResult<TData, TError = Error> {\n /** Resolved query data — non-nullable inside the rendered subtree (Suspense\n * absorbed the pending state). */\n data: TData\n /** Always `null` past the Suspense gate; errors are thrown to the nearest\n * `<ErrorBoundary>`. Typed as `TError | null` for consistency with\n * `useQuery` so the same destructure works in both. */\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n /** `true` while a background refetch is running. Use for refresh spinners. */\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Reads a query for use inside a `<Suspense>` boundary. While the query is\n * pending, throws an inflight promise (queryClient-deduplicated). On error,\n * throws the error — catch with `<ErrorBoundary>`. Returns the same shape as\n * `useQuery`, but with `data` narrowed to non-nullable `TData` since the\n * pending state is impossible past the Suspense gate.\n */\nexport function useSuspenseQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseSuspenseQueryResult<TData, TError> {\n // Auto-mount lifecycle so concurrent consumers (useUnit / useQuery) reading\n // the same query through the effector scope stay in sync.\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw observer.fetchOptimistic(observer.options as any)\n }\n\n // Read all secondary fields from the observer result, not the effector\n // stores: stores are only populated after mountFx fires from useEffect,\n // which on the very first successful render hasn't run yet. The observer\n // result is always live and consistent.\n return {\n data: result.data as TData,\n error: result.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: result.isFetching,\n isPlaceholderData: result.isPlaceholderData,\n fetchStatus: result.fetchStatus,\n refresh,\n }\n}\n\nexport interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {\n data: TData\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Suspense variant of {@link useInfiniteQuery}. Same shape as `useInfiniteQuery`,\n * with `data` narrowed to non-nullable.\n */\nexport function useSuspenseInfiniteQuery<\n TData,\n TError = Error,\n TPageParam = unknown,\n>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseSuspenseInfiniteQueryResult<TData, TError> {\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw (\n observer as unknown as {\n fetchOptimistic: (\n options: typeof observer.options,\n ) => Promise<unknown>\n }\n ).fetchOptimistic(observer.options)\n }\n\n const r = result as typeof result & {\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n }\n\n return {\n data: r.data as TData,\n error: r.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: r.isFetching,\n isPlaceholderData: r.isPlaceholderData,\n fetchStatus: r.fetchStatus,\n hasNextPage: r.hasNextPage,\n hasPreviousPage: r.hasPreviousPage,\n isFetchingNextPage: r.isFetchingNextPage,\n isFetchingPreviousPage: r.isFetchingPreviousPage,\n isFetchNextPageError: r.isFetchNextPageError,\n isFetchPreviousPageError: r.isFetchPreviousPageError,\n refresh,\n fetchNextPage,\n fetchPreviousPage,\n }\n}\n\n/**\n * Resolves a per-scope observer for suspense usage. Prefers the scope's\n * `$observer` (set by mountFx); falls back to a transient observer\n * constructed via `__createObserver` so that the very first render — before\n * useEffect has fired — has a working observer. Both flavors read/write the\n * same queryClient cache, so the transient observer is a thin wrapper.\n */\nfunction useSuspenseObserver<\n TQuery extends {\n $observer: import('effector').Store<TObserver | null>\n $queryClient: import('effector').Store<\n import('@tanstack/query-core').QueryClient | null\n >\n },\n TObserver extends {\n options: { queryKey: unknown }\n setOptions(options: any): void\n subscribe(cb: () => void): () => void\n getOptimisticResult(options: any): {\n status: 'pending' | 'success' | 'error'\n data: unknown\n error: unknown\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n // Infinite-query result fields — present at runtime when the underlying\n // observer is an InfiniteQueryObserver; the suspense hooks narrow as\n // needed. Typed as `any` here to keep the constraint loose.\n hasNextPage?: any\n hasPreviousPage?: any\n isFetchingNextPage?: any\n isFetchingPreviousPage?: any\n isFetchNextPageError?: any\n isFetchPreviousPageError?: any\n }\n fetchOptimistic(options: any): Promise<unknown>\n },\n>(query: TQuery): TObserver {\n const factory = query as unknown as TQuery & SuspenseFactory<TObserver>\n const observerInScope = useUnit(query.$observer) as TObserver | null\n const qc = useUnit(query.$queryClient)\n const queryKey = useUnit(factory.__resolvedKey)\n const enabled = useUnit(factory.__enabled)\n\n // Memoize a transient observer keyed by qc, so it survives across renders\n // while the scope observer is null. Once observerInScope appears, we\n // switch — the transient one is unsubscribed and abandoned (it never\n // subscribed to queryCache, so there is nothing to leak).\n const transient = React.useMemo(() => {\n if (observerInScope || !qc) return null\n return factory.__createObserver(qc, { queryKey, enabled })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [observerInScope, qc, factory])\n\n // Keep the transient observer's options in sync with reactive key/enabled,\n // so re-suspending on key changes still works through it.\n React.useEffect(() => {\n if (!transient) return\n transient.setOptions({ ...transient.options, queryKey, enabled })\n }, [transient, queryKey, enabled])\n\n const observer = observerInScope ?? transient\n if (!observer) {\n throw new Error(\n '[@effector-tanstack-query/react] useSuspenseQuery: no QueryClient is set. ' +\n 'Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] }).',\n )\n }\n return observer\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { QueryStatus, FetchStatus, MutateOptions } from '@tanstack/query-core';
|
|
2
|
+
import { MutationStatus, InfiniteQueryResult, MutationResult, QueryResult } from '@effector-tanstack-query/core';
|
|
3
|
+
|
|
4
|
+
interface UseQueryResult<TData, TError = Error> {
|
|
5
|
+
data: TData | undefined;
|
|
6
|
+
error: TError | null;
|
|
7
|
+
status: QueryStatus;
|
|
8
|
+
isPending: boolean;
|
|
9
|
+
isFetching: boolean;
|
|
10
|
+
isSuccess: boolean;
|
|
11
|
+
isError: boolean;
|
|
12
|
+
isPlaceholderData: boolean;
|
|
13
|
+
fetchStatus: FetchStatus;
|
|
14
|
+
refresh: () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Subscribes a React component to a query, automatically calling
|
|
18
|
+
* `mounted()` on mount and `unmounted()` on cleanup.
|
|
19
|
+
*/
|
|
20
|
+
declare function useQuery<TData, TError = Error>(query: QueryResult<TData, TError>): UseQueryResult<TData, TError>;
|
|
21
|
+
interface UseMutationResult<TData, TError, TVariables> {
|
|
22
|
+
data: TData | undefined;
|
|
23
|
+
error: TError | null;
|
|
24
|
+
status: MutationStatus;
|
|
25
|
+
variables: TVariables | undefined;
|
|
26
|
+
isPaused: boolean;
|
|
27
|
+
isPending: boolean;
|
|
28
|
+
isSuccess: boolean;
|
|
29
|
+
isError: boolean;
|
|
30
|
+
isIdle: boolean;
|
|
31
|
+
mutate: (variables: TVariables) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Trigger the mutation with per-call callbacks layered on top of the
|
|
34
|
+
* observer-level ones (`onSuccess` / `onError` / `onSettled` in
|
|
35
|
+
* `createMutation` options). Use this for component-local reactions
|
|
36
|
+
* that don't fit module-level `sample` wiring — navigation after success,
|
|
37
|
+
* one-shot toasts, etc.
|
|
38
|
+
*/
|
|
39
|
+
mutateWith: (args: {
|
|
40
|
+
variables: TVariables;
|
|
41
|
+
onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess'];
|
|
42
|
+
onError?: MutateOptions<TData, TError, TVariables>['onError'];
|
|
43
|
+
onSettled?: MutateOptions<TData, TError, TVariables>['onSettled'];
|
|
44
|
+
}) => void;
|
|
45
|
+
reset: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Subscribes a React component to a mutation, automatically calling
|
|
49
|
+
* `start()` on mount and `unmounted()` on cleanup so the queryClient can
|
|
50
|
+
* garbage-collect the mutation entry once no observers remain.
|
|
51
|
+
*/
|
|
52
|
+
declare function useMutation<TData = unknown, TError = Error, TVariables = void>(mutation: MutationResult<TData, TError, TVariables>): UseMutationResult<TData, TError, TVariables>;
|
|
53
|
+
interface UseInfiniteQueryResult<TData, TError> {
|
|
54
|
+
data: TData | undefined;
|
|
55
|
+
error: TError | null;
|
|
56
|
+
status: QueryStatus;
|
|
57
|
+
isPending: boolean;
|
|
58
|
+
isFetching: boolean;
|
|
59
|
+
isSuccess: boolean;
|
|
60
|
+
isError: boolean;
|
|
61
|
+
isPlaceholderData: boolean;
|
|
62
|
+
fetchStatus: FetchStatus;
|
|
63
|
+
hasNextPage: boolean;
|
|
64
|
+
hasPreviousPage: boolean;
|
|
65
|
+
isFetchingNextPage: boolean;
|
|
66
|
+
isFetchingPreviousPage: boolean;
|
|
67
|
+
isFetchNextPageError: boolean;
|
|
68
|
+
isFetchPreviousPageError: boolean;
|
|
69
|
+
refresh: () => void;
|
|
70
|
+
fetchNextPage: () => void;
|
|
71
|
+
fetchPreviousPage: () => void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Subscribes a React component to an infinite query, with auto mount/unmount
|
|
75
|
+
* lifecycle and bound `fetchNextPage` / `fetchPreviousPage` callbacks.
|
|
76
|
+
*/
|
|
77
|
+
declare function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(query: InfiniteQueryResult<TData, TError, TPageParam>): UseInfiniteQueryResult<TData, TError>;
|
|
78
|
+
interface UseSuspenseQueryResult<TData, TError = Error> {
|
|
79
|
+
/** Resolved query data — non-nullable inside the rendered subtree (Suspense
|
|
80
|
+
* absorbed the pending state). */
|
|
81
|
+
data: TData;
|
|
82
|
+
/** Always `null` past the Suspense gate; errors are thrown to the nearest
|
|
83
|
+
* `<ErrorBoundary>`. Typed as `TError | null` for consistency with
|
|
84
|
+
* `useQuery` so the same destructure works in both. */
|
|
85
|
+
error: TError | null;
|
|
86
|
+
status: 'success';
|
|
87
|
+
isPending: false;
|
|
88
|
+
isSuccess: true;
|
|
89
|
+
isError: false;
|
|
90
|
+
/** `true` while a background refetch is running. Use for refresh spinners. */
|
|
91
|
+
isFetching: boolean;
|
|
92
|
+
isPlaceholderData: boolean;
|
|
93
|
+
fetchStatus: FetchStatus;
|
|
94
|
+
refresh: () => void;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Reads a query for use inside a `<Suspense>` boundary. While the query is
|
|
98
|
+
* pending, throws an inflight promise (queryClient-deduplicated). On error,
|
|
99
|
+
* throws the error — catch with `<ErrorBoundary>`. Returns the same shape as
|
|
100
|
+
* `useQuery`, but with `data` narrowed to non-nullable `TData` since the
|
|
101
|
+
* pending state is impossible past the Suspense gate.
|
|
102
|
+
*/
|
|
103
|
+
declare function useSuspenseQuery<TData, TError = Error>(query: QueryResult<TData, TError>): UseSuspenseQueryResult<TData, TError>;
|
|
104
|
+
interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {
|
|
105
|
+
data: TData;
|
|
106
|
+
error: TError | null;
|
|
107
|
+
status: 'success';
|
|
108
|
+
isPending: false;
|
|
109
|
+
isSuccess: true;
|
|
110
|
+
isError: false;
|
|
111
|
+
isFetching: boolean;
|
|
112
|
+
isPlaceholderData: boolean;
|
|
113
|
+
fetchStatus: FetchStatus;
|
|
114
|
+
hasNextPage: boolean;
|
|
115
|
+
hasPreviousPage: boolean;
|
|
116
|
+
isFetchingNextPage: boolean;
|
|
117
|
+
isFetchingPreviousPage: boolean;
|
|
118
|
+
isFetchNextPageError: boolean;
|
|
119
|
+
isFetchPreviousPageError: boolean;
|
|
120
|
+
refresh: () => void;
|
|
121
|
+
fetchNextPage: () => void;
|
|
122
|
+
fetchPreviousPage: () => void;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Suspense variant of {@link useInfiniteQuery}. Same shape as `useInfiniteQuery`,
|
|
126
|
+
* with `data` narrowed to non-nullable.
|
|
127
|
+
*/
|
|
128
|
+
declare function useSuspenseInfiniteQuery<TData, TError = Error, TPageParam = unknown>(query: InfiniteQueryResult<TData, TError, TPageParam>): UseSuspenseInfiniteQueryResult<TData, TError>;
|
|
129
|
+
|
|
130
|
+
export { type UseInfiniteQueryResult, type UseMutationResult, type UseQueryResult, type UseSuspenseInfiniteQueryResult, type UseSuspenseQueryResult, useInfiniteQuery, useMutation, useQuery, useSuspenseInfiniteQuery, useSuspenseQuery };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { QueryStatus, FetchStatus, MutateOptions } from '@tanstack/query-core';
|
|
2
|
+
import { MutationStatus, InfiniteQueryResult, MutationResult, QueryResult } from '@effector-tanstack-query/core';
|
|
3
|
+
|
|
4
|
+
interface UseQueryResult<TData, TError = Error> {
|
|
5
|
+
data: TData | undefined;
|
|
6
|
+
error: TError | null;
|
|
7
|
+
status: QueryStatus;
|
|
8
|
+
isPending: boolean;
|
|
9
|
+
isFetching: boolean;
|
|
10
|
+
isSuccess: boolean;
|
|
11
|
+
isError: boolean;
|
|
12
|
+
isPlaceholderData: boolean;
|
|
13
|
+
fetchStatus: FetchStatus;
|
|
14
|
+
refresh: () => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Subscribes a React component to a query, automatically calling
|
|
18
|
+
* `mounted()` on mount and `unmounted()` on cleanup.
|
|
19
|
+
*/
|
|
20
|
+
declare function useQuery<TData, TError = Error>(query: QueryResult<TData, TError>): UseQueryResult<TData, TError>;
|
|
21
|
+
interface UseMutationResult<TData, TError, TVariables> {
|
|
22
|
+
data: TData | undefined;
|
|
23
|
+
error: TError | null;
|
|
24
|
+
status: MutationStatus;
|
|
25
|
+
variables: TVariables | undefined;
|
|
26
|
+
isPaused: boolean;
|
|
27
|
+
isPending: boolean;
|
|
28
|
+
isSuccess: boolean;
|
|
29
|
+
isError: boolean;
|
|
30
|
+
isIdle: boolean;
|
|
31
|
+
mutate: (variables: TVariables) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Trigger the mutation with per-call callbacks layered on top of the
|
|
34
|
+
* observer-level ones (`onSuccess` / `onError` / `onSettled` in
|
|
35
|
+
* `createMutation` options). Use this for component-local reactions
|
|
36
|
+
* that don't fit module-level `sample` wiring — navigation after success,
|
|
37
|
+
* one-shot toasts, etc.
|
|
38
|
+
*/
|
|
39
|
+
mutateWith: (args: {
|
|
40
|
+
variables: TVariables;
|
|
41
|
+
onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess'];
|
|
42
|
+
onError?: MutateOptions<TData, TError, TVariables>['onError'];
|
|
43
|
+
onSettled?: MutateOptions<TData, TError, TVariables>['onSettled'];
|
|
44
|
+
}) => void;
|
|
45
|
+
reset: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Subscribes a React component to a mutation, automatically calling
|
|
49
|
+
* `start()` on mount and `unmounted()` on cleanup so the queryClient can
|
|
50
|
+
* garbage-collect the mutation entry once no observers remain.
|
|
51
|
+
*/
|
|
52
|
+
declare function useMutation<TData = unknown, TError = Error, TVariables = void>(mutation: MutationResult<TData, TError, TVariables>): UseMutationResult<TData, TError, TVariables>;
|
|
53
|
+
interface UseInfiniteQueryResult<TData, TError> {
|
|
54
|
+
data: TData | undefined;
|
|
55
|
+
error: TError | null;
|
|
56
|
+
status: QueryStatus;
|
|
57
|
+
isPending: boolean;
|
|
58
|
+
isFetching: boolean;
|
|
59
|
+
isSuccess: boolean;
|
|
60
|
+
isError: boolean;
|
|
61
|
+
isPlaceholderData: boolean;
|
|
62
|
+
fetchStatus: FetchStatus;
|
|
63
|
+
hasNextPage: boolean;
|
|
64
|
+
hasPreviousPage: boolean;
|
|
65
|
+
isFetchingNextPage: boolean;
|
|
66
|
+
isFetchingPreviousPage: boolean;
|
|
67
|
+
isFetchNextPageError: boolean;
|
|
68
|
+
isFetchPreviousPageError: boolean;
|
|
69
|
+
refresh: () => void;
|
|
70
|
+
fetchNextPage: () => void;
|
|
71
|
+
fetchPreviousPage: () => void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Subscribes a React component to an infinite query, with auto mount/unmount
|
|
75
|
+
* lifecycle and bound `fetchNextPage` / `fetchPreviousPage` callbacks.
|
|
76
|
+
*/
|
|
77
|
+
declare function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(query: InfiniteQueryResult<TData, TError, TPageParam>): UseInfiniteQueryResult<TData, TError>;
|
|
78
|
+
interface UseSuspenseQueryResult<TData, TError = Error> {
|
|
79
|
+
/** Resolved query data — non-nullable inside the rendered subtree (Suspense
|
|
80
|
+
* absorbed the pending state). */
|
|
81
|
+
data: TData;
|
|
82
|
+
/** Always `null` past the Suspense gate; errors are thrown to the nearest
|
|
83
|
+
* `<ErrorBoundary>`. Typed as `TError | null` for consistency with
|
|
84
|
+
* `useQuery` so the same destructure works in both. */
|
|
85
|
+
error: TError | null;
|
|
86
|
+
status: 'success';
|
|
87
|
+
isPending: false;
|
|
88
|
+
isSuccess: true;
|
|
89
|
+
isError: false;
|
|
90
|
+
/** `true` while a background refetch is running. Use for refresh spinners. */
|
|
91
|
+
isFetching: boolean;
|
|
92
|
+
isPlaceholderData: boolean;
|
|
93
|
+
fetchStatus: FetchStatus;
|
|
94
|
+
refresh: () => void;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Reads a query for use inside a `<Suspense>` boundary. While the query is
|
|
98
|
+
* pending, throws an inflight promise (queryClient-deduplicated). On error,
|
|
99
|
+
* throws the error — catch with `<ErrorBoundary>`. Returns the same shape as
|
|
100
|
+
* `useQuery`, but with `data` narrowed to non-nullable `TData` since the
|
|
101
|
+
* pending state is impossible past the Suspense gate.
|
|
102
|
+
*/
|
|
103
|
+
declare function useSuspenseQuery<TData, TError = Error>(query: QueryResult<TData, TError>): UseSuspenseQueryResult<TData, TError>;
|
|
104
|
+
interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {
|
|
105
|
+
data: TData;
|
|
106
|
+
error: TError | null;
|
|
107
|
+
status: 'success';
|
|
108
|
+
isPending: false;
|
|
109
|
+
isSuccess: true;
|
|
110
|
+
isError: false;
|
|
111
|
+
isFetching: boolean;
|
|
112
|
+
isPlaceholderData: boolean;
|
|
113
|
+
fetchStatus: FetchStatus;
|
|
114
|
+
hasNextPage: boolean;
|
|
115
|
+
hasPreviousPage: boolean;
|
|
116
|
+
isFetchingNextPage: boolean;
|
|
117
|
+
isFetchingPreviousPage: boolean;
|
|
118
|
+
isFetchNextPageError: boolean;
|
|
119
|
+
isFetchPreviousPageError: boolean;
|
|
120
|
+
refresh: () => void;
|
|
121
|
+
fetchNextPage: () => void;
|
|
122
|
+
fetchPreviousPage: () => void;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Suspense variant of {@link useInfiniteQuery}. Same shape as `useInfiniteQuery`,
|
|
126
|
+
* with `data` narrowed to non-nullable.
|
|
127
|
+
*/
|
|
128
|
+
declare function useSuspenseInfiniteQuery<TData, TError = Error, TPageParam = unknown>(query: InfiniteQueryResult<TData, TError, TPageParam>): UseSuspenseInfiniteQueryResult<TData, TError>;
|
|
129
|
+
|
|
130
|
+
export { type UseInfiniteQueryResult, type UseMutationResult, type UseQueryResult, type UseSuspenseInfiniteQueryResult, type UseSuspenseQueryResult, useInfiniteQuery, useMutation, useQuery, useSuspenseInfiniteQuery, useSuspenseQuery };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useUnit } from 'effector-react';
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
function useQuery(query) {
|
|
6
|
+
const state = useUnit({
|
|
7
|
+
data: query.$data,
|
|
8
|
+
error: query.$error,
|
|
9
|
+
status: query.$status,
|
|
10
|
+
isPending: query.$isPending,
|
|
11
|
+
isFetching: query.$isFetching,
|
|
12
|
+
isSuccess: query.$isSuccess,
|
|
13
|
+
isError: query.$isError,
|
|
14
|
+
isPlaceholderData: query.$isPlaceholderData,
|
|
15
|
+
fetchStatus: query.$fetchStatus
|
|
16
|
+
});
|
|
17
|
+
const mount = useUnit(query.mounted);
|
|
18
|
+
const unmount = useUnit(query.unmounted);
|
|
19
|
+
const refresh = useUnit(query.refresh);
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
mount();
|
|
22
|
+
return () => unmount();
|
|
23
|
+
}, [mount, unmount]);
|
|
24
|
+
return { ...state, refresh };
|
|
25
|
+
}
|
|
26
|
+
function useMutation(mutation) {
|
|
27
|
+
const state = useUnit({
|
|
28
|
+
data: mutation.$data,
|
|
29
|
+
error: mutation.$error,
|
|
30
|
+
status: mutation.$status,
|
|
31
|
+
variables: mutation.$variables,
|
|
32
|
+
isPaused: mutation.$isPaused,
|
|
33
|
+
isPending: mutation.$isPending,
|
|
34
|
+
isSuccess: mutation.$isSuccess,
|
|
35
|
+
isError: mutation.$isError,
|
|
36
|
+
isIdle: mutation.$isIdle
|
|
37
|
+
});
|
|
38
|
+
const start = useUnit(mutation.start);
|
|
39
|
+
const unmount = useUnit(mutation.unmounted);
|
|
40
|
+
const mutate = useUnit(mutation.mutate);
|
|
41
|
+
const mutateWith = useUnit(mutation.mutateWith);
|
|
42
|
+
const reset = useUnit(mutation.reset);
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
start();
|
|
45
|
+
return () => unmount();
|
|
46
|
+
}, [start, unmount]);
|
|
47
|
+
return { ...state, mutate, mutateWith, reset };
|
|
48
|
+
}
|
|
49
|
+
function useInfiniteQuery(query) {
|
|
50
|
+
const state = useUnit({
|
|
51
|
+
data: query.$data,
|
|
52
|
+
error: query.$error,
|
|
53
|
+
status: query.$status,
|
|
54
|
+
isPending: query.$isPending,
|
|
55
|
+
isFetching: query.$isFetching,
|
|
56
|
+
isSuccess: query.$isSuccess,
|
|
57
|
+
isError: query.$isError,
|
|
58
|
+
isPlaceholderData: query.$isPlaceholderData,
|
|
59
|
+
fetchStatus: query.$fetchStatus,
|
|
60
|
+
hasNextPage: query.$hasNextPage,
|
|
61
|
+
hasPreviousPage: query.$hasPreviousPage,
|
|
62
|
+
isFetchingNextPage: query.$isFetchingNextPage,
|
|
63
|
+
isFetchingPreviousPage: query.$isFetchingPreviousPage,
|
|
64
|
+
isFetchNextPageError: query.$isFetchNextPageError,
|
|
65
|
+
isFetchPreviousPageError: query.$isFetchPreviousPageError
|
|
66
|
+
});
|
|
67
|
+
const mount = useUnit(query.mounted);
|
|
68
|
+
const unmount = useUnit(query.unmounted);
|
|
69
|
+
const refresh = useUnit(query.refresh);
|
|
70
|
+
const fetchNextPage = useUnit(query.fetchNextPage);
|
|
71
|
+
const fetchPreviousPage = useUnit(query.fetchPreviousPage);
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
mount();
|
|
74
|
+
return () => unmount();
|
|
75
|
+
}, [mount, unmount]);
|
|
76
|
+
return { ...state, refresh, fetchNextPage, fetchPreviousPage };
|
|
77
|
+
}
|
|
78
|
+
function useObserverRerender(observer) {
|
|
79
|
+
const [, forceRender] = React.useReducer((x) => x + 1, 0);
|
|
80
|
+
React.useEffect(() => {
|
|
81
|
+
if (!observer) return;
|
|
82
|
+
return observer.subscribe(forceRender);
|
|
83
|
+
}, [observer]);
|
|
84
|
+
}
|
|
85
|
+
function useSuspenseQuery(query) {
|
|
86
|
+
const mount = useUnit(query.mounted);
|
|
87
|
+
const unmount = useUnit(query.unmounted);
|
|
88
|
+
const refresh = useUnit(query.refresh);
|
|
89
|
+
React.useEffect(() => {
|
|
90
|
+
mount();
|
|
91
|
+
return () => unmount();
|
|
92
|
+
}, [mount, unmount]);
|
|
93
|
+
const observer = useSuspenseObserver(query);
|
|
94
|
+
useObserverRerender(observer);
|
|
95
|
+
const result = observer.getOptimisticResult(observer.options);
|
|
96
|
+
if (result.status === "error") throw result.error;
|
|
97
|
+
if (result.status === "pending") {
|
|
98
|
+
throw observer.fetchOptimistic(observer.options);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
data: result.data,
|
|
102
|
+
error: result.error,
|
|
103
|
+
status: "success",
|
|
104
|
+
isPending: false,
|
|
105
|
+
isSuccess: true,
|
|
106
|
+
isError: false,
|
|
107
|
+
isFetching: result.isFetching,
|
|
108
|
+
isPlaceholderData: result.isPlaceholderData,
|
|
109
|
+
fetchStatus: result.fetchStatus,
|
|
110
|
+
refresh
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function useSuspenseInfiniteQuery(query) {
|
|
114
|
+
const mount = useUnit(query.mounted);
|
|
115
|
+
const unmount = useUnit(query.unmounted);
|
|
116
|
+
const refresh = useUnit(query.refresh);
|
|
117
|
+
const fetchNextPage = useUnit(query.fetchNextPage);
|
|
118
|
+
const fetchPreviousPage = useUnit(query.fetchPreviousPage);
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
mount();
|
|
121
|
+
return () => unmount();
|
|
122
|
+
}, [mount, unmount]);
|
|
123
|
+
const observer = useSuspenseObserver(query);
|
|
124
|
+
useObserverRerender(observer);
|
|
125
|
+
const result = observer.getOptimisticResult(observer.options);
|
|
126
|
+
if (result.status === "error") throw result.error;
|
|
127
|
+
if (result.status === "pending") {
|
|
128
|
+
throw observer.fetchOptimistic(observer.options);
|
|
129
|
+
}
|
|
130
|
+
const r = result;
|
|
131
|
+
return {
|
|
132
|
+
data: r.data,
|
|
133
|
+
error: r.error,
|
|
134
|
+
status: "success",
|
|
135
|
+
isPending: false,
|
|
136
|
+
isSuccess: true,
|
|
137
|
+
isError: false,
|
|
138
|
+
isFetching: r.isFetching,
|
|
139
|
+
isPlaceholderData: r.isPlaceholderData,
|
|
140
|
+
fetchStatus: r.fetchStatus,
|
|
141
|
+
hasNextPage: r.hasNextPage,
|
|
142
|
+
hasPreviousPage: r.hasPreviousPage,
|
|
143
|
+
isFetchingNextPage: r.isFetchingNextPage,
|
|
144
|
+
isFetchingPreviousPage: r.isFetchingPreviousPage,
|
|
145
|
+
isFetchNextPageError: r.isFetchNextPageError,
|
|
146
|
+
isFetchPreviousPageError: r.isFetchPreviousPageError,
|
|
147
|
+
refresh,
|
|
148
|
+
fetchNextPage,
|
|
149
|
+
fetchPreviousPage
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function useSuspenseObserver(query) {
|
|
153
|
+
const factory = query;
|
|
154
|
+
const observerInScope = useUnit(query.$observer);
|
|
155
|
+
const qc = useUnit(query.$queryClient);
|
|
156
|
+
const queryKey = useUnit(factory.__resolvedKey);
|
|
157
|
+
const enabled = useUnit(factory.__enabled);
|
|
158
|
+
const transient = React.useMemo(() => {
|
|
159
|
+
if (observerInScope || !qc) return null;
|
|
160
|
+
return factory.__createObserver(qc, { queryKey, enabled });
|
|
161
|
+
}, [observerInScope, qc, factory]);
|
|
162
|
+
React.useEffect(() => {
|
|
163
|
+
if (!transient) return;
|
|
164
|
+
transient.setOptions({ ...transient.options, queryKey, enabled });
|
|
165
|
+
}, [transient, queryKey, enabled]);
|
|
166
|
+
const observer = observerInScope ?? transient;
|
|
167
|
+
if (!observer) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
"[@effector-tanstack-query/react] useSuspenseQuery: no QueryClient is set. Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] })."
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return observer;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { useInfiniteQuery, useMutation, useQuery, useSuspenseInfiniteQuery, useSuspenseQuery };
|
|
176
|
+
//# sourceMappingURL=index.js.map
|
|
177
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import * as React from 'react'\nimport { useUnit } from 'effector-react'\nimport type {\n FetchStatus,\n MutateOptions,\n QueryStatus,\n} from '@tanstack/query-core'\nimport type {\n InfiniteQueryResult,\n MutationResult,\n MutationStatus,\n QueryResult,\n} from '@effector-tanstack-query/core'\n\nexport interface UseQueryResult<TData, TError = Error> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Subscribes a React component to a query, automatically calling\n * `mounted()` on mount and `unmounted()` on cleanup.\n */\nexport function useQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh }\n}\n\nexport interface UseMutationResult<TData, TError, TVariables> {\n data: TData | undefined\n error: TError | null\n status: MutationStatus\n variables: TVariables | undefined\n isPaused: boolean\n isPending: boolean\n isSuccess: boolean\n isError: boolean\n isIdle: boolean\n mutate: (variables: TVariables) => void\n /**\n * Trigger the mutation with per-call callbacks layered on top of the\n * observer-level ones (`onSuccess` / `onError` / `onSettled` in\n * `createMutation` options). Use this for component-local reactions\n * that don't fit module-level `sample` wiring — navigation after success,\n * one-shot toasts, etc.\n */\n mutateWith: (args: {\n variables: TVariables\n onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess']\n onError?: MutateOptions<TData, TError, TVariables>['onError']\n onSettled?: MutateOptions<TData, TError, TVariables>['onSettled']\n }) => void\n reset: () => void\n}\n\n/**\n * Subscribes a React component to a mutation, automatically calling\n * `start()` on mount and `unmounted()` on cleanup so the queryClient can\n * garbage-collect the mutation entry once no observers remain.\n */\nexport function useMutation<TData = unknown, TError = Error, TVariables = void>(\n mutation: MutationResult<TData, TError, TVariables>,\n): UseMutationResult<TData, TError, TVariables> {\n const state = useUnit({\n data: mutation.$data,\n error: mutation.$error,\n status: mutation.$status,\n variables: mutation.$variables,\n isPaused: mutation.$isPaused,\n isPending: mutation.$isPending,\n isSuccess: mutation.$isSuccess,\n isError: mutation.$isError,\n isIdle: mutation.$isIdle,\n })\n\n const start = useUnit(mutation.start)\n const unmount = useUnit(mutation.unmounted)\n const mutate = useUnit(mutation.mutate)\n const mutateWith = useUnit(mutation.mutateWith)\n const reset = useUnit(mutation.reset)\n\n React.useEffect(() => {\n start()\n return () => unmount()\n }, [start, unmount])\n\n return { ...state, mutate, mutateWith, reset }\n}\n\nexport interface UseInfiniteQueryResult<TData, TError> {\n data: TData | undefined\n error: TError | null\n status: QueryStatus\n isPending: boolean\n isFetching: boolean\n isSuccess: boolean\n isError: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Subscribes a React component to an infinite query, with auto mount/unmount\n * lifecycle and bound `fetchNextPage` / `fetchPreviousPage` callbacks.\n */\nexport function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseInfiniteQueryResult<TData, TError> {\n const state = useUnit({\n data: query.$data,\n error: query.$error,\n status: query.$status,\n isPending: query.$isPending,\n isFetching: query.$isFetching,\n isSuccess: query.$isSuccess,\n isError: query.$isError,\n isPlaceholderData: query.$isPlaceholderData,\n fetchStatus: query.$fetchStatus,\n hasNextPage: query.$hasNextPage,\n hasPreviousPage: query.$hasPreviousPage,\n isFetchingNextPage: query.$isFetchingNextPage,\n isFetchingPreviousPage: query.$isFetchingPreviousPage,\n isFetchNextPageError: query.$isFetchNextPageError,\n isFetchPreviousPageError: query.$isFetchPreviousPageError,\n })\n\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n return { ...state, refresh, fetchNextPage, fetchPreviousPage }\n}\n\n// Suspense data path: read from a per-scope observer.\n//\n// The scope mount chain runs in useEffect, which is skipped while a component\n// is suspended — so on the very first render the scope's `$observer` may be\n// null. To get synchronous access to the observer's promise during suspense,\n// we construct a transient observer via the factory's hidden\n// `__createObserver(qc, { queryKey, enabled })` helper. The transient observer\n// reads from / writes to the same queryClient cache as the eventual scope\n// observer (which is created when mountFx runs after useEffect commits).\n//\n// The mount/unmount effect is still wired up so that other consumers reading\n// the same query through `useUnit` / `useQuery` see updates in scope state.\n\nfunction useObserverRerender(\n observer: { subscribe: (cb: () => void) => () => void } | null,\n): void {\n const [, forceRender] = React.useReducer((x: number) => x + 1, 0)\n React.useEffect(() => {\n if (!observer) return\n return observer.subscribe(forceRender)\n }, [observer])\n}\n\ninterface SuspenseFactory<TObserver> {\n __createObserver(\n qc: import('@tanstack/query-core').QueryClient,\n init: { queryKey: unknown; enabled: boolean },\n ): TObserver\n __resolvedKey: import('effector').Store<unknown>\n __enabled: import('effector').Store<boolean>\n}\n\nexport interface UseSuspenseQueryResult<TData, TError = Error> {\n /** Resolved query data — non-nullable inside the rendered subtree (Suspense\n * absorbed the pending state). */\n data: TData\n /** Always `null` past the Suspense gate; errors are thrown to the nearest\n * `<ErrorBoundary>`. Typed as `TError | null` for consistency with\n * `useQuery` so the same destructure works in both. */\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n /** `true` while a background refetch is running. Use for refresh spinners. */\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n refresh: () => void\n}\n\n/**\n * Reads a query for use inside a `<Suspense>` boundary. While the query is\n * pending, throws an inflight promise (queryClient-deduplicated). On error,\n * throws the error — catch with `<ErrorBoundary>`. Returns the same shape as\n * `useQuery`, but with `data` narrowed to non-nullable `TData` since the\n * pending state is impossible past the Suspense gate.\n */\nexport function useSuspenseQuery<TData, TError = Error>(\n query: QueryResult<TData, TError>,\n): UseSuspenseQueryResult<TData, TError> {\n // Auto-mount lifecycle so concurrent consumers (useUnit / useQuery) reading\n // the same query through the effector scope stay in sync.\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw observer.fetchOptimistic(observer.options as any)\n }\n\n // Read all secondary fields from the observer result, not the effector\n // stores: stores are only populated after mountFx fires from useEffect,\n // which on the very first successful render hasn't run yet. The observer\n // result is always live and consistent.\n return {\n data: result.data as TData,\n error: result.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: result.isFetching,\n isPlaceholderData: result.isPlaceholderData,\n fetchStatus: result.fetchStatus,\n refresh,\n }\n}\n\nexport interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {\n data: TData\n error: TError | null\n status: 'success'\n isPending: false\n isSuccess: true\n isError: false\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n refresh: () => void\n fetchNextPage: () => void\n fetchPreviousPage: () => void\n}\n\n/**\n * Suspense variant of {@link useInfiniteQuery}. Same shape as `useInfiniteQuery`,\n * with `data` narrowed to non-nullable.\n */\nexport function useSuspenseInfiniteQuery<\n TData,\n TError = Error,\n TPageParam = unknown,\n>(\n query: InfiniteQueryResult<TData, TError, TPageParam>,\n): UseSuspenseInfiniteQueryResult<TData, TError> {\n const mount = useUnit(query.mounted)\n const unmount = useUnit(query.unmounted)\n const refresh = useUnit(query.refresh)\n const fetchNextPage = useUnit(query.fetchNextPage)\n const fetchPreviousPage = useUnit(query.fetchPreviousPage)\n React.useEffect(() => {\n mount()\n return () => unmount()\n }, [mount, unmount])\n\n const observer = useSuspenseObserver(query)\n\n useObserverRerender(observer)\n\n const result = observer.getOptimisticResult(observer.options as any)\n\n if (result.status === 'error') throw result.error\n if (result.status === 'pending') {\n throw (\n observer as unknown as {\n fetchOptimistic: (\n options: typeof observer.options,\n ) => Promise<unknown>\n }\n ).fetchOptimistic(observer.options)\n }\n\n const r = result as typeof result & {\n hasNextPage: boolean\n hasPreviousPage: boolean\n isFetchingNextPage: boolean\n isFetchingPreviousPage: boolean\n isFetchNextPageError: boolean\n isFetchPreviousPageError: boolean\n }\n\n return {\n data: r.data as TData,\n error: r.error as TError | null,\n status: 'success',\n isPending: false,\n isSuccess: true,\n isError: false,\n isFetching: r.isFetching,\n isPlaceholderData: r.isPlaceholderData,\n fetchStatus: r.fetchStatus,\n hasNextPage: r.hasNextPage,\n hasPreviousPage: r.hasPreviousPage,\n isFetchingNextPage: r.isFetchingNextPage,\n isFetchingPreviousPage: r.isFetchingPreviousPage,\n isFetchNextPageError: r.isFetchNextPageError,\n isFetchPreviousPageError: r.isFetchPreviousPageError,\n refresh,\n fetchNextPage,\n fetchPreviousPage,\n }\n}\n\n/**\n * Resolves a per-scope observer for suspense usage. Prefers the scope's\n * `$observer` (set by mountFx); falls back to a transient observer\n * constructed via `__createObserver` so that the very first render — before\n * useEffect has fired — has a working observer. Both flavors read/write the\n * same queryClient cache, so the transient observer is a thin wrapper.\n */\nfunction useSuspenseObserver<\n TQuery extends {\n $observer: import('effector').Store<TObserver | null>\n $queryClient: import('effector').Store<\n import('@tanstack/query-core').QueryClient | null\n >\n },\n TObserver extends {\n options: { queryKey: unknown }\n setOptions(options: any): void\n subscribe(cb: () => void): () => void\n getOptimisticResult(options: any): {\n status: 'pending' | 'success' | 'error'\n data: unknown\n error: unknown\n isFetching: boolean\n isPlaceholderData: boolean\n fetchStatus: FetchStatus\n // Infinite-query result fields — present at runtime when the underlying\n // observer is an InfiniteQueryObserver; the suspense hooks narrow as\n // needed. Typed as `any` here to keep the constraint loose.\n hasNextPage?: any\n hasPreviousPage?: any\n isFetchingNextPage?: any\n isFetchingPreviousPage?: any\n isFetchNextPageError?: any\n isFetchPreviousPageError?: any\n }\n fetchOptimistic(options: any): Promise<unknown>\n },\n>(query: TQuery): TObserver {\n const factory = query as unknown as TQuery & SuspenseFactory<TObserver>\n const observerInScope = useUnit(query.$observer) as TObserver | null\n const qc = useUnit(query.$queryClient)\n const queryKey = useUnit(factory.__resolvedKey)\n const enabled = useUnit(factory.__enabled)\n\n // Memoize a transient observer keyed by qc, so it survives across renders\n // while the scope observer is null. Once observerInScope appears, we\n // switch — the transient one is unsubscribed and abandoned (it never\n // subscribed to queryCache, so there is nothing to leak).\n const transient = React.useMemo(() => {\n if (observerInScope || !qc) return null\n return factory.__createObserver(qc, { queryKey, enabled })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [observerInScope, qc, factory])\n\n // Keep the transient observer's options in sync with reactive key/enabled,\n // so re-suspending on key changes still works through it.\n React.useEffect(() => {\n if (!transient) return\n transient.setOptions({ ...transient.options, queryKey, enabled })\n }, [transient, queryKey, enabled])\n\n const observer = observerInScope ?? transient\n if (!observer) {\n throw new Error(\n '[@effector-tanstack-query/react] useSuspenseQuery: no QueryClient is set. ' +\n 'Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] }).',\n )\n }\n return observer\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effector-tanstack-query/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React hooks for @effector-tanstack-query/core — useQuery, useMutation, useInfiniteQuery, useSuspenseQuery, useSuspenseInfiniteQuery",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Ilya Agarkov <ilya.al.ag@gmail.com>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ilyaagarkov/effector-tanstack-query.git",
|
|
10
|
+
"directory": "packages/react"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/ilyaagarkov/effector-tanstack-query#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ilyaagarkov/effector-tanstack-query/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"effector",
|
|
18
|
+
"tanstack",
|
|
19
|
+
"query",
|
|
20
|
+
"react",
|
|
21
|
+
"hooks",
|
|
22
|
+
"suspense"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.cjs",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"import": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"require": {
|
|
35
|
+
"types": "./dist/index.d.cts",
|
|
36
|
+
"default": "./dist/index.cjs"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"src",
|
|
44
|
+
"!src/__tests__"
|
|
45
|
+
],
|
|
46
|
+
"sideEffects": false,
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"build:watch": "tsup --watch",
|
|
50
|
+
"clean": "rm -rf ./dist ./coverage",
|
|
51
|
+
"test": "vitest --run",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"test:types": "tsc --noEmit"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@effector-tanstack-query/core": "workspace:*"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@tanstack/query-core": "^5.0.0",
|
|
60
|
+
"effector": ">=23.0.0",
|
|
61
|
+
"effector-react": ">=23.0.0",
|
|
62
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { useUnit } from 'effector-react'
|
|
3
|
+
import type {
|
|
4
|
+
FetchStatus,
|
|
5
|
+
MutateOptions,
|
|
6
|
+
QueryStatus,
|
|
7
|
+
} from '@tanstack/query-core'
|
|
8
|
+
import type {
|
|
9
|
+
InfiniteQueryResult,
|
|
10
|
+
MutationResult,
|
|
11
|
+
MutationStatus,
|
|
12
|
+
QueryResult,
|
|
13
|
+
} from '@effector-tanstack-query/core'
|
|
14
|
+
|
|
15
|
+
export interface UseQueryResult<TData, TError = Error> {
|
|
16
|
+
data: TData | undefined
|
|
17
|
+
error: TError | null
|
|
18
|
+
status: QueryStatus
|
|
19
|
+
isPending: boolean
|
|
20
|
+
isFetching: boolean
|
|
21
|
+
isSuccess: boolean
|
|
22
|
+
isError: boolean
|
|
23
|
+
isPlaceholderData: boolean
|
|
24
|
+
fetchStatus: FetchStatus
|
|
25
|
+
refresh: () => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Subscribes a React component to a query, automatically calling
|
|
30
|
+
* `mounted()` on mount and `unmounted()` on cleanup.
|
|
31
|
+
*/
|
|
32
|
+
export function useQuery<TData, TError = Error>(
|
|
33
|
+
query: QueryResult<TData, TError>,
|
|
34
|
+
): UseQueryResult<TData, TError> {
|
|
35
|
+
const state = useUnit({
|
|
36
|
+
data: query.$data,
|
|
37
|
+
error: query.$error,
|
|
38
|
+
status: query.$status,
|
|
39
|
+
isPending: query.$isPending,
|
|
40
|
+
isFetching: query.$isFetching,
|
|
41
|
+
isSuccess: query.$isSuccess,
|
|
42
|
+
isError: query.$isError,
|
|
43
|
+
isPlaceholderData: query.$isPlaceholderData,
|
|
44
|
+
fetchStatus: query.$fetchStatus,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const mount = useUnit(query.mounted)
|
|
48
|
+
const unmount = useUnit(query.unmounted)
|
|
49
|
+
const refresh = useUnit(query.refresh)
|
|
50
|
+
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
mount()
|
|
53
|
+
return () => unmount()
|
|
54
|
+
}, [mount, unmount])
|
|
55
|
+
|
|
56
|
+
return { ...state, refresh }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface UseMutationResult<TData, TError, TVariables> {
|
|
60
|
+
data: TData | undefined
|
|
61
|
+
error: TError | null
|
|
62
|
+
status: MutationStatus
|
|
63
|
+
variables: TVariables | undefined
|
|
64
|
+
isPaused: boolean
|
|
65
|
+
isPending: boolean
|
|
66
|
+
isSuccess: boolean
|
|
67
|
+
isError: boolean
|
|
68
|
+
isIdle: boolean
|
|
69
|
+
mutate: (variables: TVariables) => void
|
|
70
|
+
/**
|
|
71
|
+
* Trigger the mutation with per-call callbacks layered on top of the
|
|
72
|
+
* observer-level ones (`onSuccess` / `onError` / `onSettled` in
|
|
73
|
+
* `createMutation` options). Use this for component-local reactions
|
|
74
|
+
* that don't fit module-level `sample` wiring — navigation after success,
|
|
75
|
+
* one-shot toasts, etc.
|
|
76
|
+
*/
|
|
77
|
+
mutateWith: (args: {
|
|
78
|
+
variables: TVariables
|
|
79
|
+
onSuccess?: MutateOptions<TData, TError, TVariables>['onSuccess']
|
|
80
|
+
onError?: MutateOptions<TData, TError, TVariables>['onError']
|
|
81
|
+
onSettled?: MutateOptions<TData, TError, TVariables>['onSettled']
|
|
82
|
+
}) => void
|
|
83
|
+
reset: () => void
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Subscribes a React component to a mutation, automatically calling
|
|
88
|
+
* `start()` on mount and `unmounted()` on cleanup so the queryClient can
|
|
89
|
+
* garbage-collect the mutation entry once no observers remain.
|
|
90
|
+
*/
|
|
91
|
+
export function useMutation<TData = unknown, TError = Error, TVariables = void>(
|
|
92
|
+
mutation: MutationResult<TData, TError, TVariables>,
|
|
93
|
+
): UseMutationResult<TData, TError, TVariables> {
|
|
94
|
+
const state = useUnit({
|
|
95
|
+
data: mutation.$data,
|
|
96
|
+
error: mutation.$error,
|
|
97
|
+
status: mutation.$status,
|
|
98
|
+
variables: mutation.$variables,
|
|
99
|
+
isPaused: mutation.$isPaused,
|
|
100
|
+
isPending: mutation.$isPending,
|
|
101
|
+
isSuccess: mutation.$isSuccess,
|
|
102
|
+
isError: mutation.$isError,
|
|
103
|
+
isIdle: mutation.$isIdle,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const start = useUnit(mutation.start)
|
|
107
|
+
const unmount = useUnit(mutation.unmounted)
|
|
108
|
+
const mutate = useUnit(mutation.mutate)
|
|
109
|
+
const mutateWith = useUnit(mutation.mutateWith)
|
|
110
|
+
const reset = useUnit(mutation.reset)
|
|
111
|
+
|
|
112
|
+
React.useEffect(() => {
|
|
113
|
+
start()
|
|
114
|
+
return () => unmount()
|
|
115
|
+
}, [start, unmount])
|
|
116
|
+
|
|
117
|
+
return { ...state, mutate, mutateWith, reset }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface UseInfiniteQueryResult<TData, TError> {
|
|
121
|
+
data: TData | undefined
|
|
122
|
+
error: TError | null
|
|
123
|
+
status: QueryStatus
|
|
124
|
+
isPending: boolean
|
|
125
|
+
isFetching: boolean
|
|
126
|
+
isSuccess: boolean
|
|
127
|
+
isError: boolean
|
|
128
|
+
isPlaceholderData: boolean
|
|
129
|
+
fetchStatus: FetchStatus
|
|
130
|
+
hasNextPage: boolean
|
|
131
|
+
hasPreviousPage: boolean
|
|
132
|
+
isFetchingNextPage: boolean
|
|
133
|
+
isFetchingPreviousPage: boolean
|
|
134
|
+
isFetchNextPageError: boolean
|
|
135
|
+
isFetchPreviousPageError: boolean
|
|
136
|
+
refresh: () => void
|
|
137
|
+
fetchNextPage: () => void
|
|
138
|
+
fetchPreviousPage: () => void
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Subscribes a React component to an infinite query, with auto mount/unmount
|
|
143
|
+
* lifecycle and bound `fetchNextPage` / `fetchPreviousPage` callbacks.
|
|
144
|
+
*/
|
|
145
|
+
export function useInfiniteQuery<TData, TError = Error, TPageParam = unknown>(
|
|
146
|
+
query: InfiniteQueryResult<TData, TError, TPageParam>,
|
|
147
|
+
): UseInfiniteQueryResult<TData, TError> {
|
|
148
|
+
const state = useUnit({
|
|
149
|
+
data: query.$data,
|
|
150
|
+
error: query.$error,
|
|
151
|
+
status: query.$status,
|
|
152
|
+
isPending: query.$isPending,
|
|
153
|
+
isFetching: query.$isFetching,
|
|
154
|
+
isSuccess: query.$isSuccess,
|
|
155
|
+
isError: query.$isError,
|
|
156
|
+
isPlaceholderData: query.$isPlaceholderData,
|
|
157
|
+
fetchStatus: query.$fetchStatus,
|
|
158
|
+
hasNextPage: query.$hasNextPage,
|
|
159
|
+
hasPreviousPage: query.$hasPreviousPage,
|
|
160
|
+
isFetchingNextPage: query.$isFetchingNextPage,
|
|
161
|
+
isFetchingPreviousPage: query.$isFetchingPreviousPage,
|
|
162
|
+
isFetchNextPageError: query.$isFetchNextPageError,
|
|
163
|
+
isFetchPreviousPageError: query.$isFetchPreviousPageError,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const mount = useUnit(query.mounted)
|
|
167
|
+
const unmount = useUnit(query.unmounted)
|
|
168
|
+
const refresh = useUnit(query.refresh)
|
|
169
|
+
const fetchNextPage = useUnit(query.fetchNextPage)
|
|
170
|
+
const fetchPreviousPage = useUnit(query.fetchPreviousPage)
|
|
171
|
+
|
|
172
|
+
React.useEffect(() => {
|
|
173
|
+
mount()
|
|
174
|
+
return () => unmount()
|
|
175
|
+
}, [mount, unmount])
|
|
176
|
+
|
|
177
|
+
return { ...state, refresh, fetchNextPage, fetchPreviousPage }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Suspense data path: read from a per-scope observer.
|
|
181
|
+
//
|
|
182
|
+
// The scope mount chain runs in useEffect, which is skipped while a component
|
|
183
|
+
// is suspended — so on the very first render the scope's `$observer` may be
|
|
184
|
+
// null. To get synchronous access to the observer's promise during suspense,
|
|
185
|
+
// we construct a transient observer via the factory's hidden
|
|
186
|
+
// `__createObserver(qc, { queryKey, enabled })` helper. The transient observer
|
|
187
|
+
// reads from / writes to the same queryClient cache as the eventual scope
|
|
188
|
+
// observer (which is created when mountFx runs after useEffect commits).
|
|
189
|
+
//
|
|
190
|
+
// The mount/unmount effect is still wired up so that other consumers reading
|
|
191
|
+
// the same query through `useUnit` / `useQuery` see updates in scope state.
|
|
192
|
+
|
|
193
|
+
function useObserverRerender(
|
|
194
|
+
observer: { subscribe: (cb: () => void) => () => void } | null,
|
|
195
|
+
): void {
|
|
196
|
+
const [, forceRender] = React.useReducer((x: number) => x + 1, 0)
|
|
197
|
+
React.useEffect(() => {
|
|
198
|
+
if (!observer) return
|
|
199
|
+
return observer.subscribe(forceRender)
|
|
200
|
+
}, [observer])
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface SuspenseFactory<TObserver> {
|
|
204
|
+
__createObserver(
|
|
205
|
+
qc: import('@tanstack/query-core').QueryClient,
|
|
206
|
+
init: { queryKey: unknown; enabled: boolean },
|
|
207
|
+
): TObserver
|
|
208
|
+
__resolvedKey: import('effector').Store<unknown>
|
|
209
|
+
__enabled: import('effector').Store<boolean>
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface UseSuspenseQueryResult<TData, TError = Error> {
|
|
213
|
+
/** Resolved query data — non-nullable inside the rendered subtree (Suspense
|
|
214
|
+
* absorbed the pending state). */
|
|
215
|
+
data: TData
|
|
216
|
+
/** Always `null` past the Suspense gate; errors are thrown to the nearest
|
|
217
|
+
* `<ErrorBoundary>`. Typed as `TError | null` for consistency with
|
|
218
|
+
* `useQuery` so the same destructure works in both. */
|
|
219
|
+
error: TError | null
|
|
220
|
+
status: 'success'
|
|
221
|
+
isPending: false
|
|
222
|
+
isSuccess: true
|
|
223
|
+
isError: false
|
|
224
|
+
/** `true` while a background refetch is running. Use for refresh spinners. */
|
|
225
|
+
isFetching: boolean
|
|
226
|
+
isPlaceholderData: boolean
|
|
227
|
+
fetchStatus: FetchStatus
|
|
228
|
+
refresh: () => void
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Reads a query for use inside a `<Suspense>` boundary. While the query is
|
|
233
|
+
* pending, throws an inflight promise (queryClient-deduplicated). On error,
|
|
234
|
+
* throws the error — catch with `<ErrorBoundary>`. Returns the same shape as
|
|
235
|
+
* `useQuery`, but with `data` narrowed to non-nullable `TData` since the
|
|
236
|
+
* pending state is impossible past the Suspense gate.
|
|
237
|
+
*/
|
|
238
|
+
export function useSuspenseQuery<TData, TError = Error>(
|
|
239
|
+
query: QueryResult<TData, TError>,
|
|
240
|
+
): UseSuspenseQueryResult<TData, TError> {
|
|
241
|
+
// Auto-mount lifecycle so concurrent consumers (useUnit / useQuery) reading
|
|
242
|
+
// the same query through the effector scope stay in sync.
|
|
243
|
+
const mount = useUnit(query.mounted)
|
|
244
|
+
const unmount = useUnit(query.unmounted)
|
|
245
|
+
const refresh = useUnit(query.refresh)
|
|
246
|
+
React.useEffect(() => {
|
|
247
|
+
mount()
|
|
248
|
+
return () => unmount()
|
|
249
|
+
}, [mount, unmount])
|
|
250
|
+
|
|
251
|
+
const observer = useSuspenseObserver(query)
|
|
252
|
+
|
|
253
|
+
useObserverRerender(observer)
|
|
254
|
+
|
|
255
|
+
const result = observer.getOptimisticResult(observer.options as any)
|
|
256
|
+
|
|
257
|
+
if (result.status === 'error') throw result.error
|
|
258
|
+
if (result.status === 'pending') {
|
|
259
|
+
throw observer.fetchOptimistic(observer.options as any)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Read all secondary fields from the observer result, not the effector
|
|
263
|
+
// stores: stores are only populated after mountFx fires from useEffect,
|
|
264
|
+
// which on the very first successful render hasn't run yet. The observer
|
|
265
|
+
// result is always live and consistent.
|
|
266
|
+
return {
|
|
267
|
+
data: result.data as TData,
|
|
268
|
+
error: result.error as TError | null,
|
|
269
|
+
status: 'success',
|
|
270
|
+
isPending: false,
|
|
271
|
+
isSuccess: true,
|
|
272
|
+
isError: false,
|
|
273
|
+
isFetching: result.isFetching,
|
|
274
|
+
isPlaceholderData: result.isPlaceholderData,
|
|
275
|
+
fetchStatus: result.fetchStatus,
|
|
276
|
+
refresh,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface UseSuspenseInfiniteQueryResult<TData, TError = Error> {
|
|
281
|
+
data: TData
|
|
282
|
+
error: TError | null
|
|
283
|
+
status: 'success'
|
|
284
|
+
isPending: false
|
|
285
|
+
isSuccess: true
|
|
286
|
+
isError: false
|
|
287
|
+
isFetching: boolean
|
|
288
|
+
isPlaceholderData: boolean
|
|
289
|
+
fetchStatus: FetchStatus
|
|
290
|
+
hasNextPage: boolean
|
|
291
|
+
hasPreviousPage: boolean
|
|
292
|
+
isFetchingNextPage: boolean
|
|
293
|
+
isFetchingPreviousPage: boolean
|
|
294
|
+
isFetchNextPageError: boolean
|
|
295
|
+
isFetchPreviousPageError: boolean
|
|
296
|
+
refresh: () => void
|
|
297
|
+
fetchNextPage: () => void
|
|
298
|
+
fetchPreviousPage: () => void
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Suspense variant of {@link useInfiniteQuery}. Same shape as `useInfiniteQuery`,
|
|
303
|
+
* with `data` narrowed to non-nullable.
|
|
304
|
+
*/
|
|
305
|
+
export function useSuspenseInfiniteQuery<
|
|
306
|
+
TData,
|
|
307
|
+
TError = Error,
|
|
308
|
+
TPageParam = unknown,
|
|
309
|
+
>(
|
|
310
|
+
query: InfiniteQueryResult<TData, TError, TPageParam>,
|
|
311
|
+
): UseSuspenseInfiniteQueryResult<TData, TError> {
|
|
312
|
+
const mount = useUnit(query.mounted)
|
|
313
|
+
const unmount = useUnit(query.unmounted)
|
|
314
|
+
const refresh = useUnit(query.refresh)
|
|
315
|
+
const fetchNextPage = useUnit(query.fetchNextPage)
|
|
316
|
+
const fetchPreviousPage = useUnit(query.fetchPreviousPage)
|
|
317
|
+
React.useEffect(() => {
|
|
318
|
+
mount()
|
|
319
|
+
return () => unmount()
|
|
320
|
+
}, [mount, unmount])
|
|
321
|
+
|
|
322
|
+
const observer = useSuspenseObserver(query)
|
|
323
|
+
|
|
324
|
+
useObserverRerender(observer)
|
|
325
|
+
|
|
326
|
+
const result = observer.getOptimisticResult(observer.options as any)
|
|
327
|
+
|
|
328
|
+
if (result.status === 'error') throw result.error
|
|
329
|
+
if (result.status === 'pending') {
|
|
330
|
+
throw (
|
|
331
|
+
observer as unknown as {
|
|
332
|
+
fetchOptimistic: (
|
|
333
|
+
options: typeof observer.options,
|
|
334
|
+
) => Promise<unknown>
|
|
335
|
+
}
|
|
336
|
+
).fetchOptimistic(observer.options)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const r = result as typeof result & {
|
|
340
|
+
hasNextPage: boolean
|
|
341
|
+
hasPreviousPage: boolean
|
|
342
|
+
isFetchingNextPage: boolean
|
|
343
|
+
isFetchingPreviousPage: boolean
|
|
344
|
+
isFetchNextPageError: boolean
|
|
345
|
+
isFetchPreviousPageError: boolean
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
data: r.data as TData,
|
|
350
|
+
error: r.error as TError | null,
|
|
351
|
+
status: 'success',
|
|
352
|
+
isPending: false,
|
|
353
|
+
isSuccess: true,
|
|
354
|
+
isError: false,
|
|
355
|
+
isFetching: r.isFetching,
|
|
356
|
+
isPlaceholderData: r.isPlaceholderData,
|
|
357
|
+
fetchStatus: r.fetchStatus,
|
|
358
|
+
hasNextPage: r.hasNextPage,
|
|
359
|
+
hasPreviousPage: r.hasPreviousPage,
|
|
360
|
+
isFetchingNextPage: r.isFetchingNextPage,
|
|
361
|
+
isFetchingPreviousPage: r.isFetchingPreviousPage,
|
|
362
|
+
isFetchNextPageError: r.isFetchNextPageError,
|
|
363
|
+
isFetchPreviousPageError: r.isFetchPreviousPageError,
|
|
364
|
+
refresh,
|
|
365
|
+
fetchNextPage,
|
|
366
|
+
fetchPreviousPage,
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Resolves a per-scope observer for suspense usage. Prefers the scope's
|
|
372
|
+
* `$observer` (set by mountFx); falls back to a transient observer
|
|
373
|
+
* constructed via `__createObserver` so that the very first render — before
|
|
374
|
+
* useEffect has fired — has a working observer. Both flavors read/write the
|
|
375
|
+
* same queryClient cache, so the transient observer is a thin wrapper.
|
|
376
|
+
*/
|
|
377
|
+
function useSuspenseObserver<
|
|
378
|
+
TQuery extends {
|
|
379
|
+
$observer: import('effector').Store<TObserver | null>
|
|
380
|
+
$queryClient: import('effector').Store<
|
|
381
|
+
import('@tanstack/query-core').QueryClient | null
|
|
382
|
+
>
|
|
383
|
+
},
|
|
384
|
+
TObserver extends {
|
|
385
|
+
options: { queryKey: unknown }
|
|
386
|
+
setOptions(options: any): void
|
|
387
|
+
subscribe(cb: () => void): () => void
|
|
388
|
+
getOptimisticResult(options: any): {
|
|
389
|
+
status: 'pending' | 'success' | 'error'
|
|
390
|
+
data: unknown
|
|
391
|
+
error: unknown
|
|
392
|
+
isFetching: boolean
|
|
393
|
+
isPlaceholderData: boolean
|
|
394
|
+
fetchStatus: FetchStatus
|
|
395
|
+
// Infinite-query result fields — present at runtime when the underlying
|
|
396
|
+
// observer is an InfiniteQueryObserver; the suspense hooks narrow as
|
|
397
|
+
// needed. Typed as `any` here to keep the constraint loose.
|
|
398
|
+
hasNextPage?: any
|
|
399
|
+
hasPreviousPage?: any
|
|
400
|
+
isFetchingNextPage?: any
|
|
401
|
+
isFetchingPreviousPage?: any
|
|
402
|
+
isFetchNextPageError?: any
|
|
403
|
+
isFetchPreviousPageError?: any
|
|
404
|
+
}
|
|
405
|
+
fetchOptimistic(options: any): Promise<unknown>
|
|
406
|
+
},
|
|
407
|
+
>(query: TQuery): TObserver {
|
|
408
|
+
const factory = query as unknown as TQuery & SuspenseFactory<TObserver>
|
|
409
|
+
const observerInScope = useUnit(query.$observer) as TObserver | null
|
|
410
|
+
const qc = useUnit(query.$queryClient)
|
|
411
|
+
const queryKey = useUnit(factory.__resolvedKey)
|
|
412
|
+
const enabled = useUnit(factory.__enabled)
|
|
413
|
+
|
|
414
|
+
// Memoize a transient observer keyed by qc, so it survives across renders
|
|
415
|
+
// while the scope observer is null. Once observerInScope appears, we
|
|
416
|
+
// switch — the transient one is unsubscribed and abandoned (it never
|
|
417
|
+
// subscribed to queryCache, so there is nothing to leak).
|
|
418
|
+
const transient = React.useMemo(() => {
|
|
419
|
+
if (observerInScope || !qc) return null
|
|
420
|
+
return factory.__createObserver(qc, { queryKey, enabled })
|
|
421
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
422
|
+
}, [observerInScope, qc, factory])
|
|
423
|
+
|
|
424
|
+
// Keep the transient observer's options in sync with reactive key/enabled,
|
|
425
|
+
// so re-suspending on key changes still works through it.
|
|
426
|
+
React.useEffect(() => {
|
|
427
|
+
if (!transient) return
|
|
428
|
+
transient.setOptions({ ...transient.options, queryKey, enabled })
|
|
429
|
+
}, [transient, queryKey, enabled])
|
|
430
|
+
|
|
431
|
+
const observer = observerInScope ?? transient
|
|
432
|
+
if (!observer) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
'[@effector-tanstack-query/react] useSuspenseQuery: no QueryClient is set. ' +
|
|
435
|
+
'Call setQueryClient(qc) or pass it to fork({ values: [[$queryClient, qc]] }).',
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
return observer
|
|
439
|
+
}
|