Skip to content

Commit d27d4dd

Browse files
author
Andrei Fidelman
committed
Replace useFragment with Apollo's native
1 parent b1b0933 commit d27d4dd

4 files changed

Lines changed: 232 additions & 152 deletions

File tree

examples/apollo-watch-fragments/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"@apollo/client": "~3.6.0",
6+
"@apollo/client": "3.8.1",
77
"@graphitation/apollo-react-relay-duct-tape": "^1.3.17",
88
"@graphitation/graphql-js-tag": "^0.9.4",
99
"@graphql-tools/schema": "^9.0.19",

packages/apollo-react-relay-duct-tape/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
"duct-tape-compiler": "yarn duct-tape-compiler:compiled-hooks && yarn duct-tape-compiler:noop-hooks"
2121
},
2222
"devDependencies": {
23-
"@apollo/client": ">= ^3.3.0 < 3.7.0",
23+
"@apollo/client": "3.8.1",
2424
"@graphitation/apollo-mock-client": "^0.11.10",
25+
"@graphitation/apollo-react-relay-duct-tape-compiler": "^1.6.15",
2526
"@graphitation/graphql-js-operation-payload-generator": "^0.12.10",
2627
"@graphitation/graphql-js-tag": "^0.9.4",
2728
"@types/jest": "^26.0.22",
@@ -30,11 +31,9 @@
3031
"graphql": "^15.0.0",
3132
"monorepo-scripts": "*",
3233
"react": "^18.2.0",
33-
"@graphitation/apollo-react-relay-duct-tape-compiler": "^1.6.15",
3434
"ts-expect": "^1.3.0"
3535
},
3636
"peerDependencies": {
37-
"@apollo/client": ">= ^3.3.0 < 3.7.0",
3837
"graphql": "^15.0.0",
3938
"react": "^18.2.0"
4039
},
Lines changed: 99 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
import { useEffect, useMemo } from "react";
21
import invariant from "invariant";
3-
import { useForceUpdate } from "./useForceUpdate";
4-
import { useOverridenOrDefaultApolloClient } from "../../useOverridenOrDefaultApolloClient";
52

63
import type { FragmentReference } from "./types";
7-
import type { CompiledArtefactModule } from "@graphitation/apollo-react-relay-duct-tape-compiler";
8-
import { Cache } from "@apollo/client/core";
4+
import type {
5+
CompiledArtefactModule,
6+
Metadata,
7+
} from "@graphitation/apollo-react-relay-duct-tape-compiler";
8+
import { DocumentNode } from "@apollo/client/core";
9+
import { useFragment, UseFragmentOptions } from "@apollo/client";
10+
import { Kind, ValueNode } from "graphql";
911

1012
/**
1113
* @param documents Compiled watch query document that is used to setup a narrow
1214
* observable for just the data selected by the original fragment.
1315
* @param fragmentReference A Node object that has a globally unique `id` field.
1416
*/
15-
1617
export function useCompiledFragment(
1718
documents: CompiledArtefactModule,
1819
fragmentReference: FragmentReference,
19-
): object {
20+
) {
2021
invariant(
2122
fragmentReference,
2223
"useFragment(): Expected metadata to have been extracted from " +
@@ -28,73 +29,102 @@ export function useCompiledFragment(
2829
"useFragment(): Expected a `watchQueryDocument` to have been " +
2930
"extracted. Did you forget to invoke the compiler?",
3031
);
32+
const from = fragmentReference.id;
33+
invariant(
34+
typeof from === "string",
35+
"useFragment(): Expected the fragment reference to have a string id. " +
36+
"Did you forget to invoke the compiler?",
37+
);
38+
invariant(
39+
metadata,
40+
"useFragment(): Expected metadata to have been extracted from " +
41+
"the fragment. Did you forget to invoke the compiler?",
42+
);
3143

32-
const client = useOverridenOrDefaultApolloClient();
33-
const forceUpdate = useForceUpdate();
44+
const defaultVariables = getDefaultVariables(watchQueryDocument);
45+
const variables = {
46+
...defaultVariables,
47+
...fragmentReference.__fragments,
48+
};
3449

35-
const observableQuery = useMemo(
36-
() =>
37-
client.watchQuery({
38-
fetchPolicy: "cache-only",
39-
query: watchQueryDocument,
40-
returnPartialData: false,
41-
variables: {
42-
...fragmentReference.__fragments,
43-
id: fragmentReference.id,
44-
__fragments: fragmentReference.__fragments,
45-
},
46-
}),
47-
[client, fragmentReference.id, fragmentReference.__fragments],
48-
);
50+
const doc: UseFragmentOptions<unknown, unknown> = {
51+
fragment: getFragmentDocumentFromWatchQueryDocument(
52+
watchQueryDocument,
53+
metadata,
54+
),
55+
fragmentName: metadata?.mainFragment?.name,
56+
from,
57+
variables,
58+
};
4959

50-
useEffect(() => {
51-
let skipFirst = true;
52-
const subscription = observableQuery.subscribe(
53-
() => {
54-
// Unclear why, but this yields twice with the same results, so skip one.
55-
if (skipFirst) {
56-
skipFirst = false;
57-
} else {
58-
forceUpdate();
59-
}
60-
},
61-
(error) => {
62-
console.log(error);
63-
},
64-
);
65-
return () => subscription.unsubscribe();
66-
}, [observableQuery]);
60+
const result = useFragment(doc);
61+
const data = result.complete ? result.data : {};
62+
return data as object;
63+
}
6764

68-
const result = observableQuery.getCurrentResult();
69-
if (result.partial) {
70-
invariant(
71-
false,
72-
"useFragment(): Missing data expected to be seeded by the execution query document: %s. Please check your type policies and possibleTypes configuration. If only subset of properties is missing you might need to configure merge functions for non-normalized types.",
73-
JSON.stringify(
74-
// we need the cast because queryInfo and lastDiff are private but very useful for debugging
75-
(
76-
observableQuery as unknown as {
77-
queryInfo?: { lastDiff?: { diff?: Cache.DiffResult<unknown> } };
78-
}
79-
).queryInfo?.lastDiff?.diff?.missing?.map((e) => e.path),
80-
),
81-
);
82-
}
83-
let data = result.data;
84-
if (metadata?.rootSelection) {
85-
invariant(
86-
data,
87-
"useFragment(): Expected Apollo to respond with previously seeded data of the execution query document: %s. Did you configure your type policies and possibleTypes correctly? Check apollo-react-relay-duct-tape README for more details.",
88-
JSON.stringify({
89-
selection: metadata.rootSelection,
90-
mainFragment: metadata.mainFragment,
91-
}),
92-
);
93-
data = data[metadata.rootSelection];
65+
type DefaultValue =
66+
| string
67+
| number
68+
| boolean
69+
| { [key: string]: DefaultValue }
70+
| DefaultValue[]
71+
| undefined;
72+
73+
const extractValue = (node: ValueNode): DefaultValue => {
74+
if (!node) return undefined;
75+
76+
switch (node.kind) {
77+
case Kind.INT:
78+
case Kind.FLOAT:
79+
return Number(node.value);
80+
case Kind.STRING:
81+
case Kind.ENUM:
82+
case Kind.BOOLEAN:
83+
return node.value;
84+
case Kind.LIST:
85+
return node.values.map(extractValue);
86+
case Kind.OBJECT:
87+
return node.fields.reduce<Record<string, DefaultValue>>((obj, field) => {
88+
obj[field.name.value] = extractValue(field.value);
89+
return obj;
90+
}, {});
91+
default:
92+
return undefined;
9493
}
94+
};
95+
96+
function getDefaultVariables(documentNode: DocumentNode) {
97+
const variableDefinitions = documentNode.definitions
98+
.filter((def) => def.kind === Kind.OPERATION_DEFINITION)
99+
.flatMap((def) => def.variableDefinitions || []);
100+
101+
return variableDefinitions.reduce<Record<string, DefaultValue>>(
102+
(acc, varDef) => {
103+
if (varDef.defaultValue) {
104+
acc[varDef.variable.name.value] = extractValue(varDef.defaultValue);
105+
}
106+
return acc;
107+
},
108+
{},
109+
);
110+
}
111+
function getFragmentDocumentFromWatchQueryDocument(
112+
watchQueryDocument: DocumentNode,
113+
metadata: Metadata,
114+
): DocumentNode {
115+
const fragmentDefinition = watchQueryDocument.definitions.find((def) => {
116+
if (def.kind === "FragmentDefinition") {
117+
return def.name.value === metadata?.mainFragment?.name;
118+
}
119+
return false;
120+
});
95121
invariant(
96-
data,
97-
"useFragment(): Expected Apollo to respond with previously seeded data of the execution query document. Did you configure your type policies and possibleTypes correctly? Check apollo-react-relay-duct-tape README for more details.",
122+
fragmentDefinition,
123+
"useFragment(): Expected a fragment definition to be found in the " +
124+
"watch query document. Did you forget to invoke the compiler?",
98125
);
99-
return data;
126+
return {
127+
kind: "Document",
128+
definitions: [fragmentDefinition],
129+
};
100130
}

0 commit comments

Comments
 (0)