@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 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"]}
@@ -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 };
@@ -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
+ }