@sylphx/lens-react 2.0.2 → 2.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/dist/index.d.ts +49 -42
- package/dist/index.js +29 -17
- package/package.json +1 -1
- package/src/hooks.test.tsx +425 -314
- package/src/hooks.ts +119 -88
- package/src/index.ts +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ declare function LensProvider({ client, children }: LensProviderProps): ReactEle
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
declare function useLensClient<TRouter = any>(): LensClient<any, any> & TRouter;
|
|
47
|
-
import { MutationResult, QueryResult } from "@sylphx/lens-client";
|
|
47
|
+
import { LensClient as LensClient2, MutationResult, QueryResult } from "@sylphx/lens-client";
|
|
48
48
|
import { DependencyList } from "react";
|
|
49
49
|
/** Result of useQuery hook */
|
|
50
50
|
interface UseQueryResult<T> {
|
|
@@ -83,39 +83,44 @@ interface UseQueryOptions<
|
|
|
83
83
|
/** Transform the query result */
|
|
84
84
|
select?: (data: TData) => TSelected;
|
|
85
85
|
}
|
|
86
|
-
/**
|
|
87
|
-
type
|
|
86
|
+
/** Client type for callbacks */
|
|
87
|
+
type Client = LensClient2<any, any>;
|
|
88
|
+
/** Route selector - callback that returns a route function */
|
|
89
|
+
type RouteSelector<
|
|
88
90
|
TParams,
|
|
89
91
|
TResult
|
|
90
|
-
> = (params: TParams) => QueryResult<TResult
|
|
91
|
-
/**
|
|
92
|
-
type
|
|
93
|
-
/** Mutation function
|
|
94
|
-
type
|
|
92
|
+
> = (client: Client) => ((params: TParams) => QueryResult<TResult>) | null;
|
|
93
|
+
/** Query accessor selector - callback that returns QueryResult */
|
|
94
|
+
type QuerySelector<TResult> = (client: Client) => QueryResult<TResult> | null | undefined;
|
|
95
|
+
/** Mutation selector - callback that returns mutation function */
|
|
96
|
+
type MutationSelector<
|
|
95
97
|
TInput,
|
|
96
98
|
TOutput
|
|
97
|
-
> = (input: TInput) => Promise<MutationResult<TOutput>>;
|
|
99
|
+
> = (client: Client) => (input: TInput) => Promise<MutationResult<TOutput>>;
|
|
98
100
|
/**
|
|
99
101
|
* Subscribe to a query with reactive updates.
|
|
102
|
+
* Client is automatically injected from LensProvider context.
|
|
100
103
|
*
|
|
101
104
|
* Two usage patterns:
|
|
102
105
|
*
|
|
103
106
|
* **1. Route + Params (recommended)** - Stable references, no infinite loops
|
|
104
107
|
* ```tsx
|
|
105
|
-
* const { data } = useQuery(client.user.get, { id: userId });
|
|
108
|
+
* const { data } = useQuery((client) => client.user.get, { id: userId });
|
|
106
109
|
* ```
|
|
107
110
|
*
|
|
108
111
|
* **2. Accessor + Deps (escape hatch)** - For complex/composed queries
|
|
109
112
|
* ```tsx
|
|
110
|
-
* const { data } = useQuery(() => client.user.get({ id })
|
|
113
|
+
* const { data } = useQuery((client) => client.user.get({ id }), [id]);
|
|
111
114
|
* ```
|
|
112
115
|
*
|
|
113
116
|
* @example
|
|
114
117
|
* ```tsx
|
|
115
118
|
* // Basic usage - Route + Params
|
|
116
119
|
* function UserProfile({ userId }: { userId: string }) {
|
|
117
|
-
* const
|
|
118
|
-
*
|
|
120
|
+
* const { data: user, loading, error } = useQuery(
|
|
121
|
+
* (client) => client.user.get,
|
|
122
|
+
* { id: userId }
|
|
123
|
+
* );
|
|
119
124
|
*
|
|
120
125
|
* if (loading) return <Spinner />;
|
|
121
126
|
* if (error) return <Error message={error.message} />;
|
|
@@ -124,34 +129,36 @@ type MutationFn<
|
|
|
124
129
|
*
|
|
125
130
|
* // With select transform
|
|
126
131
|
* function UserName({ userId }: { userId: string }) {
|
|
127
|
-
* const
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
132
|
+
* const { data: name } = useQuery(
|
|
133
|
+
* (client) => client.user.get,
|
|
134
|
+
* { id: userId },
|
|
135
|
+
* { select: (user) => user.name }
|
|
136
|
+
* );
|
|
131
137
|
* return <span>{name}</span>;
|
|
132
138
|
* }
|
|
133
139
|
*
|
|
134
|
-
* // Conditional query
|
|
140
|
+
* // Conditional query (return null to skip)
|
|
135
141
|
* function SessionInfo({ sessionId }: { sessionId: string | null }) {
|
|
136
|
-
* const client = useLensClient();
|
|
137
142
|
* const { data } = useQuery(
|
|
138
|
-
* sessionId ? client.session.get : null,
|
|
143
|
+
* (client) => sessionId ? client.session.get : null,
|
|
139
144
|
* { id: sessionId ?? '' }
|
|
140
145
|
* );
|
|
141
146
|
* return <span>{data?.totalTokens}</span>;
|
|
142
147
|
* }
|
|
143
148
|
*
|
|
144
|
-
* // Skip query
|
|
149
|
+
* // Skip query with option
|
|
145
150
|
* function ConditionalQuery({ userId, shouldFetch }: { userId: string; shouldFetch: boolean }) {
|
|
146
|
-
* const
|
|
147
|
-
*
|
|
151
|
+
* const { data } = useQuery(
|
|
152
|
+
* (client) => client.user.get,
|
|
153
|
+
* { id: userId },
|
|
154
|
+
* { skip: !shouldFetch }
|
|
155
|
+
* );
|
|
148
156
|
* }
|
|
149
157
|
*
|
|
150
158
|
* // Complex queries with accessor (escape hatch)
|
|
151
159
|
* function ComplexQuery({ userId }: { userId: string }) {
|
|
152
|
-
* const client = useLensClient();
|
|
153
160
|
* const { data } = useQuery(
|
|
154
|
-
* () => client.user.get({ id: userId }),
|
|
161
|
+
* (client) => client.user.get({ id: userId }),
|
|
155
162
|
* [userId]
|
|
156
163
|
* );
|
|
157
164
|
* }
|
|
@@ -161,21 +168,23 @@ declare function useQuery<
|
|
|
161
168
|
TParams,
|
|
162
169
|
TResult,
|
|
163
170
|
TSelected = TResult
|
|
164
|
-
>(
|
|
171
|
+
>(selector: RouteSelector<TParams, TResult>, params: TParams, options?: UseQueryOptions<TResult, TSelected>): UseQueryResult<TSelected>;
|
|
165
172
|
declare function useQuery<
|
|
166
173
|
TResult,
|
|
167
174
|
TSelected = TResult
|
|
168
|
-
>(
|
|
175
|
+
>(selector: QuerySelector<TResult>, deps: DependencyList, options?: UseQueryOptions<TResult, TSelected>): UseQueryResult<TSelected>;
|
|
169
176
|
/**
|
|
170
|
-
* Execute mutations with loading/error state
|
|
177
|
+
* Execute mutations with loading/error state.
|
|
178
|
+
* Client is automatically injected from LensProvider context.
|
|
171
179
|
*
|
|
172
|
-
* @param
|
|
180
|
+
* @param selector - Callback that returns mutation function from client
|
|
173
181
|
*
|
|
174
182
|
* @example
|
|
175
183
|
* \`\`\`tsx
|
|
176
184
|
* function CreatePost() {
|
|
177
|
-
* const
|
|
178
|
-
*
|
|
185
|
+
* const { mutate, loading, error, data } = useMutation(
|
|
186
|
+
* (client) => client.post.create
|
|
187
|
+
* );
|
|
179
188
|
*
|
|
180
189
|
* const handleSubmit = async (formData: FormData) => {
|
|
181
190
|
* try {
|
|
@@ -201,8 +210,7 @@ declare function useQuery<
|
|
|
201
210
|
*
|
|
202
211
|
* // With optimistic updates
|
|
203
212
|
* function UpdatePost({ postId }: { postId: string }) {
|
|
204
|
-
* const
|
|
205
|
-
* const { mutate } = useMutation(client.post.update);
|
|
213
|
+
* const { mutate } = useMutation((client) => client.post.update);
|
|
206
214
|
*
|
|
207
215
|
* const handleUpdate = async (title: string) => {
|
|
208
216
|
* const result = await mutate({ id: postId, title });
|
|
@@ -214,7 +222,7 @@ declare function useQuery<
|
|
|
214
222
|
declare function useMutation<
|
|
215
223
|
TInput,
|
|
216
224
|
TOutput
|
|
217
|
-
>(
|
|
225
|
+
>(selector: MutationSelector<TInput, TOutput>): UseMutationResult<TInput, TOutput>;
|
|
218
226
|
/** Result of useLazyQuery hook */
|
|
219
227
|
interface UseLazyQueryResult<T> {
|
|
220
228
|
/** Execute the query */
|
|
@@ -229,16 +237,16 @@ interface UseLazyQueryResult<T> {
|
|
|
229
237
|
reset: () => void;
|
|
230
238
|
}
|
|
231
239
|
/**
|
|
232
|
-
* Execute a query on demand (not on mount)
|
|
240
|
+
* Execute a query on demand (not on mount).
|
|
241
|
+
* Client is automatically injected from LensProvider context.
|
|
233
242
|
*
|
|
234
243
|
* @example
|
|
235
244
|
* ```tsx
|
|
236
245
|
* // Route + Params pattern
|
|
237
246
|
* function SearchUsers() {
|
|
238
|
-
* const client = useLensClient();
|
|
239
247
|
* const [searchTerm, setSearchTerm] = useState('');
|
|
240
248
|
* const { execute, data, loading } = useLazyQuery(
|
|
241
|
-
* client.user.search,
|
|
249
|
+
* (client) => client.user.search,
|
|
242
250
|
* { query: searchTerm }
|
|
243
251
|
* );
|
|
244
252
|
*
|
|
@@ -253,9 +261,8 @@ interface UseLazyQueryResult<T> {
|
|
|
253
261
|
*
|
|
254
262
|
* // Accessor pattern
|
|
255
263
|
* function LazyComplexQuery({ userId }: { userId: string }) {
|
|
256
|
-
* const client = useLensClient();
|
|
257
264
|
* const { execute, data } = useLazyQuery(
|
|
258
|
-
* () => client.user.get({ id: userId }),
|
|
265
|
+
* (client) => client.user.get({ id: userId }),
|
|
259
266
|
* [userId]
|
|
260
267
|
* );
|
|
261
268
|
* return <button onClick={execute}>Load</button>;
|
|
@@ -266,9 +273,9 @@ declare function useLazyQuery<
|
|
|
266
273
|
TParams,
|
|
267
274
|
TResult,
|
|
268
275
|
TSelected = TResult
|
|
269
|
-
>(
|
|
276
|
+
>(selector: RouteSelector<TParams, TResult>, params: TParams, options?: UseQueryOptions<TResult, TSelected>): UseLazyQueryResult<TSelected>;
|
|
270
277
|
declare function useLazyQuery<
|
|
271
278
|
TResult,
|
|
272
279
|
TSelected = TResult
|
|
273
|
-
>(
|
|
274
|
-
export { useQuery, useMutation, useLensClient, useLazyQuery, UseQueryResult, UseQueryOptions, UseMutationResult, UseLazyQueryResult,
|
|
280
|
+
>(selector: QuerySelector<TResult>, deps: DependencyList, options?: UseQueryOptions<TResult, TSelected>): UseLazyQueryResult<TSelected>;
|
|
281
|
+
export { useQuery, useMutation, useLensClient, useLazyQuery, UseQueryResult, UseQueryOptions, UseMutationResult, UseLazyQueryResult, RouteSelector, QuerySelector, MutationSelector, LensProviderProps, LensProvider };
|
package/dist/index.js
CHANGED
|
@@ -17,21 +17,23 @@ function useLensClient() {
|
|
|
17
17
|
}
|
|
18
18
|
// src/hooks.ts
|
|
19
19
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
20
|
-
function useQuery(
|
|
20
|
+
function useQuery(selector, paramsOrDeps, options) {
|
|
21
|
+
const client = useLensClient();
|
|
21
22
|
const isAccessorMode = Array.isArray(paramsOrDeps);
|
|
22
23
|
const paramsKey = !isAccessorMode ? JSON.stringify(paramsOrDeps) : null;
|
|
23
24
|
const query = useMemo(() => {
|
|
24
25
|
if (options?.skip)
|
|
25
26
|
return null;
|
|
26
27
|
if (isAccessorMode) {
|
|
27
|
-
const
|
|
28
|
-
return
|
|
28
|
+
const querySelector = selector;
|
|
29
|
+
return querySelector(client);
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
const routeSelector = selector;
|
|
32
|
+
const route = routeSelector(client);
|
|
33
|
+
if (!route)
|
|
31
34
|
return null;
|
|
32
|
-
const route = routeOrAccessor;
|
|
33
35
|
return route(paramsOrDeps);
|
|
34
|
-
}, isAccessorMode ? [options?.skip, ...paramsOrDeps] : [
|
|
36
|
+
}, isAccessorMode ? [client, options?.skip, ...paramsOrDeps] : [client, selector, paramsKey, options?.skip]);
|
|
35
37
|
const selectRef = useRef(options?.select);
|
|
36
38
|
selectRef.current = options?.select;
|
|
37
39
|
const [data, setData] = useState(null);
|
|
@@ -53,15 +55,19 @@ function useQuery(routeOrAccessor, paramsOrDeps, options) {
|
|
|
53
55
|
}
|
|
54
56
|
setLoading(true);
|
|
55
57
|
setError(null);
|
|
58
|
+
let hasReceivedData = false;
|
|
56
59
|
const unsubscribe = query.subscribe((value) => {
|
|
57
60
|
if (mountedRef.current) {
|
|
61
|
+
hasReceivedData = true;
|
|
58
62
|
setData(transform(value));
|
|
59
63
|
setLoading(false);
|
|
60
64
|
}
|
|
61
65
|
});
|
|
62
66
|
query.then((value) => {
|
|
63
|
-
if (mountedRef.current) {
|
|
67
|
+
if (mountedRef.current && !hasReceivedData) {
|
|
64
68
|
setData(transform(value));
|
|
69
|
+
}
|
|
70
|
+
if (mountedRef.current) {
|
|
65
71
|
setLoading(false);
|
|
66
72
|
}
|
|
67
73
|
}, (err) => {
|
|
@@ -95,11 +101,15 @@ function useQuery(routeOrAccessor, paramsOrDeps, options) {
|
|
|
95
101
|
}, [transform]);
|
|
96
102
|
return { data, loading, error, refetch };
|
|
97
103
|
}
|
|
98
|
-
function useMutation(
|
|
104
|
+
function useMutation(selector) {
|
|
105
|
+
const client = useLensClient();
|
|
106
|
+
const mutationFn = selector(client);
|
|
99
107
|
const [loading, setLoading] = useState(false);
|
|
100
108
|
const [error, setError] = useState(null);
|
|
101
109
|
const [data, setData] = useState(null);
|
|
102
110
|
const mountedRef = useRef(true);
|
|
111
|
+
const mutationRef = useRef(mutationFn);
|
|
112
|
+
mutationRef.current = mutationFn;
|
|
103
113
|
useEffect(() => {
|
|
104
114
|
mountedRef.current = true;
|
|
105
115
|
return () => {
|
|
@@ -110,7 +120,7 @@ function useMutation(mutationFn) {
|
|
|
110
120
|
setLoading(true);
|
|
111
121
|
setError(null);
|
|
112
122
|
try {
|
|
113
|
-
const result = await
|
|
123
|
+
const result = await mutationRef.current(input);
|
|
114
124
|
if (mountedRef.current) {
|
|
115
125
|
setData(result.data);
|
|
116
126
|
}
|
|
@@ -126,7 +136,7 @@ function useMutation(mutationFn) {
|
|
|
126
136
|
setLoading(false);
|
|
127
137
|
}
|
|
128
138
|
}
|
|
129
|
-
}, [
|
|
139
|
+
}, []);
|
|
130
140
|
const reset = useCallback(() => {
|
|
131
141
|
setLoading(false);
|
|
132
142
|
setError(null);
|
|
@@ -134,14 +144,15 @@ function useMutation(mutationFn) {
|
|
|
134
144
|
}, []);
|
|
135
145
|
return { mutate, loading, error, data, reset };
|
|
136
146
|
}
|
|
137
|
-
function useLazyQuery(
|
|
147
|
+
function useLazyQuery(selector, paramsOrDeps, options) {
|
|
148
|
+
const client = useLensClient();
|
|
138
149
|
const [data, setData] = useState(null);
|
|
139
150
|
const [loading, setLoading] = useState(false);
|
|
140
151
|
const [error, setError] = useState(null);
|
|
141
152
|
const mountedRef = useRef(true);
|
|
142
153
|
const isAccessorMode = Array.isArray(paramsOrDeps);
|
|
143
|
-
const
|
|
144
|
-
|
|
154
|
+
const selectorRef = useRef(selector);
|
|
155
|
+
selectorRef.current = selector;
|
|
145
156
|
const paramsOrDepsRef = useRef(paramsOrDeps);
|
|
146
157
|
paramsOrDepsRef.current = paramsOrDeps;
|
|
147
158
|
const selectRef = useRef(options?.select);
|
|
@@ -155,10 +166,11 @@ function useLazyQuery(routeOrAccessor, paramsOrDeps, options) {
|
|
|
155
166
|
const execute = useCallback(async () => {
|
|
156
167
|
let query;
|
|
157
168
|
if (isAccessorMode) {
|
|
158
|
-
const
|
|
159
|
-
query =
|
|
169
|
+
const querySelector = selectorRef.current;
|
|
170
|
+
query = querySelector(client);
|
|
160
171
|
} else {
|
|
161
|
-
const
|
|
172
|
+
const routeSelector = selectorRef.current;
|
|
173
|
+
const route = routeSelector(client);
|
|
162
174
|
if (route) {
|
|
163
175
|
query = route(paramsOrDepsRef.current);
|
|
164
176
|
}
|
|
@@ -188,7 +200,7 @@ function useLazyQuery(routeOrAccessor, paramsOrDeps, options) {
|
|
|
188
200
|
setLoading(false);
|
|
189
201
|
}
|
|
190
202
|
}
|
|
191
|
-
}, [isAccessorMode]);
|
|
203
|
+
}, [client, isAccessorMode]);
|
|
192
204
|
const reset = useCallback(() => {
|
|
193
205
|
setLoading(false);
|
|
194
206
|
setError(null);
|