Skip to content

Commit 1b02a00

Browse files
committed
feat: Support flattened result structure
1 parent 8c721c0 commit 1b02a00

File tree

7 files changed

+148
-27
lines changed

7 files changed

+148
-27
lines changed

examples/react-example/src/firebase.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ if (getApps().length === 0) {
77
projectId: "example",
88
});
99
const dataConnect = getDataConnect(connectorConfig);
10-
connectDataConnectEmulator(dataConnect!, "localhost", 5003);
10+
connectDataConnectEmulator(dataConnect!, "localhost", 9399);
1111
}

packages/react/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
"license": "Apache-2.0",
3333
"devDependencies": {
3434
"@testing-library/react": "^16.0.1",
35-
"react": "^18.3.1",
36-
"@types/react": "^18.3.5",
35+
"react": "^19.0.0",
36+
"@types/react": "^19.0.1",
3737
"@dataconnect/default-connector": "workspace:*"
3838
},
3939
"peerDependencies": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export { DataConnectQueryClient } from "./query-client";
12
export { useConnectQuery } from "./useConnectQuery";
23
export { useConnectMutation } from "./useConnectMutation";
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {
2+
QueryClient,
3+
QueryKey,
4+
type FetchQueryOptions,
5+
} from "@tanstack/react-query";
6+
import type { FirebaseError } from "firebase/app";
7+
import type { FlattenedQueryResult } from "./types";
8+
import { executeQuery, QueryRef, QueryResult } from "firebase/data-connect";
9+
10+
type DataConnectQueryOptions<Data, Variables> = Omit<
11+
FetchQueryOptions<
12+
FlattenedQueryResult<Data, Variables>,
13+
FirebaseError,
14+
FlattenedQueryResult<Data, Variables>,
15+
QueryKey
16+
>,
17+
"queryFn" | "queryKey"
18+
> & {
19+
queryRef: QueryRef<Data, Variables>;
20+
queryKey?: QueryKey;
21+
};
22+
23+
export class DataConnectQueryClient extends QueryClient {
24+
prefetchDataConnectQuery<Data extends Record<string, any>, Variables>(
25+
refOrResult: QueryRef<Data, Variables> | QueryResult<Data, Variables>,
26+
options?: DataConnectQueryOptions<Data, Variables>
27+
) {
28+
let queryRef: QueryRef<Data, Variables>;
29+
let initialData: FlattenedQueryResult<Data, Variables> | undefined;
30+
31+
if ("ref" in refOrResult) {
32+
queryRef = refOrResult.ref;
33+
initialData = {
34+
...refOrResult.data,
35+
ref: refOrResult.ref,
36+
source: refOrResult.source,
37+
fetchTime: refOrResult.fetchTime,
38+
};
39+
} else {
40+
queryRef = refOrResult;
41+
}
42+
43+
return this.prefetchQuery<
44+
FlattenedQueryResult<Data, Variables>,
45+
FirebaseError,
46+
FlattenedQueryResult<Data, Variables>,
47+
QueryKey
48+
>({
49+
...options,
50+
initialData,
51+
queryKey: options?.queryKey ?? [
52+
queryRef.name,
53+
queryRef.variables || null,
54+
],
55+
queryFn: async () => {
56+
const response = await executeQuery(queryRef);
57+
58+
const data = {
59+
...response.data,
60+
ref: response.ref,
61+
source: response.source,
62+
fetchTime: response.fetchTime,
63+
};
64+
65+
// Ensures no serialization issues with undefined values
66+
return JSON.parse(JSON.stringify(data));
67+
},
68+
});
69+
}
70+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { MutationResult, QueryResult } from "firebase/data-connect";
2+
3+
// Flattens a QueryResult data down into a single object.
4+
// This is to prevent query.data.data, and also expose additional properties.
5+
export type FlattenedQueryResult<Data, Variables> = Omit<
6+
QueryResult<Data, Variables>,
7+
"data" | "toJSON"
8+
> &
9+
Data;
10+
11+
export type FlattenedMutationResult<Data, Variables> = Omit<
12+
MutationResult<Data, Variables>,
13+
"data" | "toJSON"
14+
> &
15+
Data;

packages/react/src/data-connect/useConnectMutation.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
useMutation,
33
useQueryClient,
44
type UseMutationOptions,
5-
type UseMutationResult,
65
} from "@tanstack/react-query";
76
import { type FirebaseError } from "firebase/app";
87
import {
@@ -11,13 +10,16 @@ import {
1110
type DataConnect,
1211
type QueryRef,
1312
} from "firebase/data-connect";
13+
import { FlattenedMutationResult } from "./types";
1414

1515
type UseConnectMutationOptions<
1616
TData = unknown,
1717
TError = FirebaseError,
1818
Variables = unknown
1919
> = Omit<UseMutationOptions<TData, TError, Variables>, "mutationFn"> & {
20-
invalidate?: QueryRef<unknown, unknown>[];
20+
invalidate?: Array<
21+
QueryRef<unknown, unknown> | (() => QueryRef<unknown, unknown>)
22+
>;
2123
};
2224

2325
export function useConnectMutation<
@@ -33,26 +35,47 @@ export function useConnectMutation<
3335
: never
3436
>(
3537
ref: Fn,
36-
options?: UseConnectMutationOptions<Data, FirebaseError, Variables>
37-
): UseMutationResult<Data, FirebaseError, Variables> {
38+
options?: UseConnectMutationOptions<
39+
FlattenedMutationResult<Data, Variables>,
40+
FirebaseError,
41+
Variables
42+
>
43+
) {
3844
const queryClient = useQueryClient();
3945

40-
return useMutation<Data, FirebaseError, Variables>({
46+
return useMutation<
47+
FlattenedMutationResult<Data, Variables>,
48+
FirebaseError,
49+
Variables
50+
>({
4151
...options,
4252
onSuccess(...args) {
4353
if (options?.invalidate && options.invalidate.length) {
4454
for (const ref of options.invalidate) {
45-
queryClient.invalidateQueries({
46-
queryKey: [ref.name, ref.variables],
47-
});
55+
if ("variables" in ref) {
56+
queryClient.invalidateQueries({
57+
queryKey: [ref.name, ref.variables || null],
58+
exact: true,
59+
});
60+
} else {
61+
queryClient.invalidateQueries({
62+
queryKey: [ref().name],
63+
});
64+
}
4865
}
4966
}
5067

5168
options?.onSuccess?.(...args);
5269
},
53-
mutationFn: async (variables: Variables) => {
54-
const { data } = await executeMutation<Data, Variables>(ref(variables));
55-
return data;
70+
mutationFn: async (variables) => {
71+
const response = await executeMutation<Data, Variables>(ref(variables));
72+
73+
return {
74+
...response.data,
75+
ref: response.ref,
76+
source: response.source,
77+
fetchTime: response.fetchTime,
78+
};
5679
},
5780
});
5881
}
Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useQuery, type UseQueryOptions } from "@tanstack/react-query";
2-
import { type FirebaseError } from "firebase/app";
2+
import type { FirebaseError } from "firebase/app";
33
import type { PartialBy } from "../../utils";
4+
import type { FlattenedQueryResult } from "./types";
45
import {
56
type QueryRef,
67
type QueryResult,
@@ -12,30 +13,41 @@ type UseConnectQueryOptions<
1213
TError = FirebaseError
1314
> = PartialBy<Omit<UseQueryOptions<TData, TError>, "queryFn">, "queryKey">;
1415

15-
export function useConnectQuery<
16-
Data extends Record<string, any>,
17-
Variables = unknown
18-
>(
16+
export function useConnectQuery<Data = unknown, Variables = unknown>(
1917
refOrResult: QueryRef<Data, Variables> | QueryResult<Data, Variables>,
20-
options?: UseConnectQueryOptions<Data, FirebaseError>
18+
options?: UseConnectQueryOptions<
19+
FlattenedQueryResult<Data, Variables>,
20+
FirebaseError
21+
>
2122
) {
2223
let queryRef: QueryRef<Data, Variables>;
23-
let initialData: Data | undefined;
24+
let initialData: FlattenedQueryResult<Data, Variables> | undefined;
2425

2526
if ("ref" in refOrResult) {
2627
queryRef = refOrResult.ref;
27-
initialData = refOrResult.data;
28+
initialData = {
29+
...refOrResult.data,
30+
ref: refOrResult.ref,
31+
source: refOrResult.source,
32+
fetchTime: refOrResult.fetchTime,
33+
};
2834
} else {
2935
queryRef = refOrResult;
3036
}
3137

32-
return useQuery<Data, FirebaseError>({
33-
initialData,
38+
return useQuery<FlattenedQueryResult<Data, Variables>, FirebaseError>({
3439
...options,
35-
queryKey: options?.queryKey ?? [queryRef.name, queryRef.variables],
40+
initialData,
41+
queryKey: options?.queryKey ?? [queryRef.name, queryRef.variables || null],
3642
queryFn: async () => {
37-
const { data } = await executeQuery<Data, Variables>(queryRef);
38-
return data;
43+
const response = await executeQuery<Data, Variables>(queryRef);
44+
45+
return {
46+
...response.data,
47+
ref: response.ref,
48+
source: response.source,
49+
fetchTime: response.fetchTime,
50+
};
3951
},
4052
});
4153
}

0 commit comments

Comments
 (0)