relay-runtime 20.1.1 → 21.0.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.
Files changed (262) hide show
  1. package/experimental.js +1 -1
  2. package/experimental.js.flow +8 -8
  3. package/handlers/connection/ConnectionHandler.js.flow +5 -5
  4. package/handlers/connection/ConnectionInterface.js.flow +1 -1
  5. package/index.js +1 -1
  6. package/index.js.flow +125 -62
  7. package/lib/experimental.js +3 -3
  8. package/lib/index.js +105 -57
  9. package/lib/multi-actor-environment/ActorIdentifier.js +2 -2
  10. package/lib/multi-actor-environment/MultiActorEnvironment.js +3 -1
  11. package/lib/mutations/commitMutation.js +8 -8
  12. package/lib/mutations/validateMutation.js +4 -4
  13. package/lib/query/GraphQLTag.js +3 -3
  14. package/lib/query/fetchQuery.js +15 -3
  15. package/lib/store/DataChecker.js +38 -4
  16. package/lib/store/NormalizationEngine.js +373 -0
  17. package/lib/store/OperationExecutor.js +172 -113
  18. package/lib/store/RelayConcreteVariables.js +1 -1
  19. package/lib/store/RelayErrorTrie.js +2 -2
  20. package/lib/store/RelayExperimentalGraphResponseTransform.js +8 -8
  21. package/lib/store/RelayModernEnvironment.js +26 -19
  22. package/lib/store/RelayModernRecord.js +18 -8
  23. package/lib/store/RelayModernSelector.js +9 -9
  24. package/lib/store/RelayModernStore.js +152 -43
  25. package/lib/store/RelayPublishQueue.js +1 -1
  26. package/lib/store/RelayReader.js +76 -38
  27. package/lib/store/RelayRecordSource.js +6 -0
  28. package/lib/store/RelayReferenceMarker.js +2 -1
  29. package/lib/store/RelayResponseNormalizer.js +88 -55
  30. package/lib/store/RelayStoreSubscriptions.js +34 -10
  31. package/lib/store/RelayStoreUtils.js +8 -1
  32. package/lib/store/ResolverFragments.js +2 -2
  33. package/lib/store/live-resolvers/LiveResolverCache.js +25 -9
  34. package/lib/store/observeFragmentExperimental.js +17 -1
  35. package/lib/store/observeQueryExperimental.js +2 -2
  36. package/lib/subscription/requestSubscription.js +3 -3
  37. package/lib/util/RelayError.js +3 -0
  38. package/lib/util/RelayFeatureFlags.js +6 -2
  39. package/lib/util/RelayReplaySubject.js +4 -4
  40. package/lib/util/handlePotentialSnapshotErrors.js +2 -2
  41. package/lib/util/stableCopy.js +2 -2
  42. package/llm-docs/api-reference/entrypoint-apis/entrypoint-container.mdx +38 -0
  43. package/llm-docs/api-reference/entrypoint-apis/load-entrypoint.mdx +77 -0
  44. package/llm-docs/api-reference/entrypoint-apis/use-entrypoint-loader.mdx +99 -0
  45. package/llm-docs/api-reference/graphql/graphql-directives.mdx +378 -0
  46. package/llm-docs/api-reference/hooks/_use-lazy-load-query-extra.mdx +16 -0
  47. package/llm-docs/api-reference/hooks/load-query.mdx +84 -0
  48. package/llm-docs/api-reference/hooks/relay-environment-provider.mdx +78 -0
  49. package/llm-docs/api-reference/hooks/use-client-query.mdx +65 -0
  50. package/llm-docs/api-reference/hooks/use-fragment.mdx +69 -0
  51. package/llm-docs/api-reference/hooks/use-lazy-load-query.mdx +62 -0
  52. package/llm-docs/api-reference/hooks/use-mutation.mdx +94 -0
  53. package/llm-docs/api-reference/hooks/use-pagination-fragment.mdx +166 -0
  54. package/llm-docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.mdx +134 -0
  55. package/llm-docs/api-reference/hooks/use-preloaded-query.mdx +84 -0
  56. package/llm-docs/api-reference/hooks/use-query-loader.mdx +95 -0
  57. package/llm-docs/api-reference/hooks/use-refetchable-fragment.mdx +122 -0
  58. package/llm-docs/api-reference/hooks/use-relay-environment.mdx +37 -0
  59. package/llm-docs/api-reference/hooks/use-subscription.mdx +66 -0
  60. package/llm-docs/api-reference/relay-resolvers/docblock-format.mdx +321 -0
  61. package/llm-docs/api-reference/relay-resolvers/runtime-functions.mdx +94 -0
  62. package/llm-docs/api-reference/relay-runtime/commit-mutation.mdx +65 -0
  63. package/llm-docs/api-reference/relay-runtime/fetch-query.mdx +113 -0
  64. package/llm-docs/api-reference/relay-runtime/field-logger.mdx +170 -0
  65. package/llm-docs/api-reference/relay-runtime/observe-fragment.mdx +92 -0
  66. package/llm-docs/api-reference/relay-runtime/relay-environment.mdx +53 -0
  67. package/llm-docs/api-reference/relay-runtime/request-subscription.mdx +54 -0
  68. package/llm-docs/api-reference/relay-runtime/runtime-configuration.mdx +52 -0
  69. package/llm-docs/api-reference/relay-runtime/store.mdx +734 -0
  70. package/llm-docs/api-reference/relay-runtime/wait-for-fragment-data.mdx +89 -0
  71. package/llm-docs/api-reference/types/CacheConfig.mdx +8 -0
  72. package/llm-docs/api-reference/types/Disposable.mdx +4 -0
  73. package/llm-docs/api-reference/types/GraphQLSubscriptionConfig.mdx +17 -0
  74. package/llm-docs/api-reference/types/MutationConfig.mdx +31 -0
  75. package/llm-docs/api-reference/types/SelectorStoreUpdater.mdx +6 -0
  76. package/llm-docs/api-reference/types/UploadableMap.mdx +3 -0
  77. package/llm-docs/community/learning-resources.mdx +64 -0
  78. package/llm-docs/debugging/declarative-mutation-directives.mdx +34 -0
  79. package/llm-docs/debugging/disallowed-id-types-error.mdx +43 -0
  80. package/llm-docs/debugging/inconsistent-typename-error.mdx +47 -0
  81. package/llm-docs/debugging/relay-devtools.mdx +73 -0
  82. package/llm-docs/debugging/why-null.mdx +116 -0
  83. package/llm-docs/editor-support.mdx +55 -0
  84. package/llm-docs/error-reference/unknown-field.mdx +36 -0
  85. package/llm-docs/getting-started/babel-plugin.mdx +31 -0
  86. package/llm-docs/getting-started/compiler-config.mdx +25 -0
  87. package/llm-docs/getting-started/compiler.mdx +82 -0
  88. package/llm-docs/getting-started/lint-rules.mdx +87 -0
  89. package/llm-docs/getting-started/production.mdx +30 -0
  90. package/llm-docs/getting-started/quick-start.mdx +213 -0
  91. package/llm-docs/glossary/glossary.mdx +1040 -0
  92. package/llm-docs/guided-tour/list-data/advanced-pagination.mdx +157 -0
  93. package/llm-docs/guided-tour/list-data/connections.mdx +81 -0
  94. package/llm-docs/guided-tour/list-data/pagination.mdx +193 -0
  95. package/llm-docs/guided-tour/list-data/rendering-connections.mdx +112 -0
  96. package/llm-docs/guided-tour/list-data/streaming-pagination.mdx +87 -0
  97. package/llm-docs/guided-tour/managing-data-outside-react/retaining-queries.mdx +51 -0
  98. package/llm-docs/guided-tour/refetching/refetching-queries-with-different-data.mdx +337 -0
  99. package/llm-docs/guided-tour/refetching/refreshing-queries.mdx +350 -0
  100. package/llm-docs/guided-tour/rendering/environment.mdx +59 -0
  101. package/llm-docs/guided-tour/rendering/error-states.mdx +295 -0
  102. package/llm-docs/guided-tour/rendering/fragments.mdx +354 -0
  103. package/llm-docs/guided-tour/rendering/loading-states.mdx +245 -0
  104. package/llm-docs/guided-tour/rendering/queries.mdx +261 -0
  105. package/llm-docs/guided-tour/rendering/variables.mdx +233 -0
  106. package/llm-docs/guided-tour/reusing-cached-data/fetch-policies.mdx +56 -0
  107. package/llm-docs/guided-tour/reusing-cached-data/filling-in-missing-data.mdx +102 -0
  108. package/llm-docs/guided-tour/reusing-cached-data/introduction.mdx +22 -0
  109. package/llm-docs/guided-tour/reusing-cached-data/presence-of-data.mdx +93 -0
  110. package/llm-docs/guided-tour/reusing-cached-data/rendering-partially-cached-data.mdx +175 -0
  111. package/llm-docs/guided-tour/reusing-cached-data/staleness-of-data.mdx +116 -0
  112. package/llm-docs/guided-tour/updating-data/client-only-data.mdx +115 -0
  113. package/llm-docs/guided-tour/updating-data/graphql-mutations.mdx +334 -0
  114. package/llm-docs/guided-tour/updating-data/graphql-subscriptions.mdx +279 -0
  115. package/llm-docs/guided-tour/updating-data/imperatively-modifying-linked-fields.mdx +511 -0
  116. package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data-legacy.mdx +142 -0
  117. package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data.mdx +275 -0
  118. package/llm-docs/guided-tour/updating-data/introduction.mdx +25 -0
  119. package/llm-docs/guided-tour/updating-data/local-data-updates.mdx +71 -0
  120. package/llm-docs/guided-tour/updating-data/typesafe-updaters-faq.mdx +83 -0
  121. package/llm-docs/guided-tour/updating-data/updating-connections.mdx +592 -0
  122. package/llm-docs/guides/alias-directive.mdx +160 -0
  123. package/llm-docs/guides/catch-directive.mdx +167 -0
  124. package/llm-docs/guides/client-schema-extensions.mdx +208 -0
  125. package/llm-docs/guides/codemods.mdx +66 -0
  126. package/llm-docs/guides/data-driven-dependencies/client-3d.mdx +255 -0
  127. package/llm-docs/guides/data-driven-dependencies/configuration.mdx +127 -0
  128. package/llm-docs/guides/data-driven-dependencies/introduction.mdx +39 -0
  129. package/llm-docs/guides/data-driven-dependencies/server-3d.mdx +664 -0
  130. package/llm-docs/guides/document-comparison.mdx +106 -0
  131. package/llm-docs/guides/graphql-server-specification.mdx +453 -0
  132. package/llm-docs/guides/network-layer.mdx +69 -0
  133. package/llm-docs/guides/persisted-queries.mdx +328 -0
  134. package/llm-docs/guides/relay-resolvers/context.mdx +99 -0
  135. package/llm-docs/guides/relay-resolvers/defining-fields.mdx +151 -0
  136. package/llm-docs/guides/relay-resolvers/defining-types.mdx +164 -0
  137. package/llm-docs/guides/relay-resolvers/deprecated.mdx +27 -0
  138. package/llm-docs/guides/relay-resolvers/derived-fields.mdx +127 -0
  139. package/llm-docs/guides/relay-resolvers/descriptions.mdx +44 -0
  140. package/llm-docs/guides/relay-resolvers/enabling.mdx +41 -0
  141. package/llm-docs/guides/relay-resolvers/errors.mdx +64 -0
  142. package/llm-docs/guides/relay-resolvers/field-arguments.mdx +63 -0
  143. package/llm-docs/guides/relay-resolvers/introduction.mdx +62 -0
  144. package/llm-docs/guides/relay-resolvers/limitations.mdx +30 -0
  145. package/llm-docs/guides/relay-resolvers/live-fields.mdx +164 -0
  146. package/llm-docs/guides/relay-resolvers/return-types.mdx +161 -0
  147. package/llm-docs/guides/relay-resolvers/suspense.mdx +41 -0
  148. package/llm-docs/guides/required-directive.mdx +240 -0
  149. package/llm-docs/guides/semantic-nullability.mdx +93 -0
  150. package/llm-docs/guides/testing-relay-components.mdx +642 -0
  151. package/llm-docs/guides/testing-relay-with-preloaded-queries.mdx +160 -0
  152. package/llm-docs/guides/throw-on-field-error-directive.mdx +58 -0
  153. package/llm-docs/guides/type-emission.mdx +414 -0
  154. package/llm-docs/home.mdx +32 -0
  155. package/llm-docs/principles-and-architecture/architecture-overview.mdx +24 -0
  156. package/llm-docs/principles-and-architecture/compiler-architecture.mdx +106 -0
  157. package/llm-docs/principles-and-architecture/runtime-architecture.mdx +249 -0
  158. package/llm-docs/principles-and-architecture/thinking-in-graphql.mdx +309 -0
  159. package/llm-docs/principles-and-architecture/thinking-in-relay.mdx +104 -0
  160. package/llm-docs/principles-and-architecture/videos.mdx +50 -0
  161. package/llm-docs/tutorial/arrays-lists.mdx +126 -0
  162. package/llm-docs/tutorial/fragments-1.mdx +487 -0
  163. package/llm-docs/tutorial/graphql.mdx +172 -0
  164. package/llm-docs/tutorial/interfaces-polymorphism.mdx +161 -0
  165. package/llm-docs/tutorial/intro.mdx +58 -0
  166. package/llm-docs/tutorial/mutations-updates.mdx +624 -0
  167. package/llm-docs/tutorial/organizing-mutations-queries-and-subscriptions.mdx +13 -0
  168. package/llm-docs/tutorial/queries-1.mdx +267 -0
  169. package/llm-docs/tutorial/queries-2.mdx +389 -0
  170. package/llm-docs/tutorial/refetchable-fragments.mdx +352 -0
  171. package/multi-actor-environment/ActorIdentifier.js.flow +2 -2
  172. package/multi-actor-environment/ActorSpecificEnvironment.js.flow +7 -7
  173. package/multi-actor-environment/ActorUtils.js.flow +1 -1
  174. package/multi-actor-environment/MultiActorEnvironment.js.flow +12 -8
  175. package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +3 -3
  176. package/mutations/RelayDeclarativeMutationConfig.js.flow +9 -9
  177. package/mutations/RelayRecordProxy.js.flow +8 -11
  178. package/mutations/RelayRecordSourceMutator.js.flow +4 -4
  179. package/mutations/RelayRecordSourceProxy.js.flow +4 -4
  180. package/mutations/RelayRecordSourceSelectorProxy.js.flow +6 -6
  181. package/mutations/applyOptimisticMutation.js.flow +2 -2
  182. package/mutations/commitMutation.js.flow +20 -16
  183. package/mutations/createUpdatableProxy.js.flow +19 -19
  184. package/mutations/readUpdatableFragment.js.flow +3 -3
  185. package/mutations/readUpdatableQuery.js.flow +3 -3
  186. package/mutations/validateMutation.js.flow +7 -7
  187. package/network/RelayNetworkTypes.js.flow +4 -4
  188. package/network/RelayObservable.js.flow +16 -14
  189. package/network/RelayQueryResponseCache.js.flow +3 -3
  190. package/network/wrapNetworkWithLogObserver.js.flow +1 -1
  191. package/package.json +2 -1
  192. package/query/GraphQLTag.js.flow +22 -10
  193. package/query/fetchQuery.js.flow +23 -10
  194. package/query/fetchQuery_DEPRECATED.js.flow +1 -1
  195. package/store/DataChecker.js.flow +43 -9
  196. package/store/NormalizationEngine.js.flow +779 -0
  197. package/store/OperationExecutor.js.flow +173 -70
  198. package/store/RelayConcreteVariables.js.flow +5 -5
  199. package/store/RelayErrorTrie.js.flow +12 -12
  200. package/store/RelayExperimentalGraphResponseHandler.js.flow +3 -3
  201. package/store/RelayExperimentalGraphResponseTransform.js.flow +10 -10
  202. package/store/RelayModernEnvironment.js.flow +41 -26
  203. package/store/RelayModernFragmentSpecResolver.js.flow +1 -1
  204. package/store/RelayModernOperationDescriptor.js.flow +1 -1
  205. package/store/RelayModernRecord.js.flow +44 -20
  206. package/store/RelayModernSelector.js.flow +21 -21
  207. package/store/RelayModernStore.js.flow +219 -58
  208. package/store/RelayOperationTracker.js.flow +2 -2
  209. package/store/RelayOptimisticRecordSource.js.flow +2 -2
  210. package/store/RelayPublishQueue.js.flow +21 -12
  211. package/store/RelayReader.js.flow +129 -57
  212. package/store/RelayRecordSource.js.flow +10 -0
  213. package/store/RelayRecordState.js.flow +1 -1
  214. package/store/RelayReferenceMarker.js.flow +5 -4
  215. package/store/RelayResponseNormalizer.js.flow +125 -57
  216. package/store/RelayStoreSubscriptions.js.flow +52 -8
  217. package/store/RelayStoreTypes.js.flow +153 -64
  218. package/store/RelayStoreUtils.js.flow +15 -7
  219. package/store/ResolverCache.js.flow +2 -2
  220. package/store/ResolverFragments.js.flow +12 -12
  221. package/store/StoreInspector.js.flow +6 -7
  222. package/store/cloneRelayHandleSourceField.js.flow +1 -1
  223. package/store/cloneRelayScalarHandleSourceField.js.flow +1 -1
  224. package/store/createRelayContext.js.flow +1 -1
  225. package/store/createRelayLoggingContext.js.flow +4 -4
  226. package/store/defaultGetDataID.js.flow +2 -2
  227. package/store/isRelayModernEnvironment.js.flow +4 -2
  228. package/store/live-resolvers/LiveResolverCache.js.flow +55 -20
  229. package/store/live-resolvers/LiveResolverSuspenseSentinel.js.flow +3 -3
  230. package/store/live-resolvers/getOutputTypeRecordIDs.js.flow +1 -1
  231. package/store/live-resolvers/isLiveStateValue.js.flow +2 -2
  232. package/store/live-resolvers/resolverDataInjector.js.flow +8 -5
  233. package/store/observeFragmentExperimental.js.flow +49 -20
  234. package/store/observeQueryExperimental.js.flow +5 -5
  235. package/store/readInlineData.js.flow +4 -4
  236. package/store/waitForFragmentExperimental.js.flow +3 -3
  237. package/subscription/requestSubscription.js.flow +7 -7
  238. package/util/NormalizationNode.js.flow +34 -32
  239. package/util/ReaderNode.js.flow +32 -30
  240. package/util/RelayConcreteNode.js.flow +5 -5
  241. package/util/RelayError.js.flow +4 -1
  242. package/util/RelayFeatureFlags.js.flow +21 -1
  243. package/util/RelayProfiler.js.flow +1 -1
  244. package/util/RelayReplaySubject.js.flow +3 -3
  245. package/util/RelayRuntimeTypes.js.flow +11 -11
  246. package/util/createPayloadFor3DField.js.flow +9 -5
  247. package/util/deepFreeze.js.flow +2 -2
  248. package/util/getFragmentIdentifier.js.flow +1 -1
  249. package/util/getPaginationMetadata.js.flow +1 -1
  250. package/util/getPaginationVariables.js.flow +1 -1
  251. package/util/getPendingOperationsForFragment.js.flow +2 -2
  252. package/util/getRefetchMetadata.js.flow +6 -5
  253. package/util/getValueAtPath.js.flow +3 -3
  254. package/util/handlePotentialSnapshotErrors.js.flow +5 -5
  255. package/util/isEmptyObject.js.flow +1 -1
  256. package/util/isPromise.js.flow +2 -2
  257. package/util/isScalarAndEqual.js.flow +1 -1
  258. package/util/recycleNodesInto.js.flow +2 -2
  259. package/util/registerEnvironmentWithDevTools.js.flow +1 -1
  260. package/util/shallowFreeze.js.flow +1 -1
  261. package/util/stableCopy.js.flow +5 -5
  262. package/util/withProvidedVariables.js.flow +14 -10
@@ -0,0 +1,352 @@
1
+ # Refetchable Fragments
2
+
3
+ In this section, we'll look at how to fetch different data in response to user input.
4
+
5
+ * We'll build a **filterable friends list**.
6
+ * We'll see how to refetch only the necessary data, not an entire query.
7
+
8
+ * * *
9
+
10
+ Since Relay encourages you to fetch all of your data in one big query, what happens when you need to refetch some data with different variables?
11
+
12
+ For example, suppose you were building a filterable list. You would need to fetch new search results when the search input changed.
13
+
14
+ One way to approach this would be to use a separate, secondary query to fetch the list, much like we did to fetch the hovercard earlier. Then we could change the query variables and refetch the query when the input changed.
15
+
16
+ However, this isn't optimal, because it needlessly uses a second query to fetch the *initial* list, before any user input occurs. The hovercard only appeared in response to a user interaction, but if the filterable list is visible and ready to be filtered, we might as well get its initial contents as part of our big query.
17
+
18
+ On the other hand, we don't want to refetch the *entire big query* whenever the input changes. Not only would this mean retrieving a large amount of data unnecessarily, it could disrupt other parts of the UI. If certain data unrelated to the filterable list has changed on the server, it would appear to randomly change when the query was refetched. Besides, this would mean threading the user input up to the top of the React tree where the query lives, which would not scale very well.
19
+
20
+ To address these issues, Relay provides *refetchable fragments*. These are fragments that can be refetched with new variables, separately from the rest of the query that they get spread into. They allow us to change a fragment’s arguments and fetch new data for the new argument values, just as we can fetch an entire query with new query variables.
21
+
22
+ But fragments are just that, fragments — they aren’t queries and can’t be fetched without being spread into a query and read out from the query results. So how do refetchable fragments actually work? The answer is that the Relay compiler generates a new, separate query just to refetch the fragment. The data is retrieved *initially* as part of whatever larger query the fragment is spread into, but then when it’s refetched, the new synthetic query is used.
23
+
24
+ * * *
25
+
26
+ To try this out, let's add a sidebar to the page with a filterable contacts list. After all, it wouldn't feel like a properly cozy newsfeed app without the ability to contact people.
27
+
28
+ We've already prepared a `Sidebar` component, you just need to drop it into `App.tsx`:
29
+
30
+ ```
31
+ // change-line
32
+ import Sidebar from './Sidebar';
33
+
34
+ export default function App(): React.ReactElement {
35
+ return (
36
+ <RelayEnvironment>
37
+ <React.Suspense fallback={<LoadingSpinner />}>
38
+ <div className="app">
39
+ <Newsfeed />
40
+ // change-line
41
+ <Sidebar />
42
+ </div>
43
+ </React.Suspense>
44
+ </RelayEnvironment>
45
+ );
46
+ }
47
+ ```
48
+
49
+ You should now see a sidebar with a list of people at the top.
50
+
51
+ ![Contacts list](/img/docs/tutorial/refetchable-contacts-initial.png)
52
+
53
+ Have a look at `ContactsList.tsx` and you’ll find this fragment, which is what selects the list of contacts:
54
+
55
+ ```
56
+ const ContactsListFragment = graphql`
57
+ fragment ContactsListFragment on Viewer {
58
+ contacts {
59
+ id
60
+ ...ContactRowFragment
61
+ }
62
+ }
63
+ `;
64
+ ```
65
+
66
+ As it happens, the `contacts` field accepts a `search` argument that filters the list. You can try it out by changing `contacts` in this fragment to `contacts(search: "S")`. If you run `npm run relay` and refresh the page, you should see only those contacts that have the letter S in them.
67
+
68
+ Our goal, then, will be to hook up a search input so that, when the input changes, we refetch *just this fragment* with a new value for that `search` argument.
69
+
70
+ :::tip
71
+ As an optional exercise, try combining the queries of Sidebar and Newsfeed into a single query. There is no need for Sidebar to have its own query separate from Newsfeed; in a real app they would both have fragments and the *entire screen* would have only a single query. We built it with a separate query to simplify the early examples in the tutorial.
72
+ :::
73
+
74
+ ### Step 1 — Add a fragment argument
75
+
76
+ First we need to make this fragment accept an argument. With refetchable fragments, fragment arguments become query variables for the refetch query that Relay generates. (They also work like regular fragment arguments, so the parent query can pass in an initial value for the argument.)
77
+
78
+ ```
79
+ const ContactsListFragment = graphql`
80
+ fragment ContactsListFragment on Viewer
81
+ // change
82
+ @argumentDefinitions(
83
+ search: {type: "String", defaultValue: null}
84
+ )
85
+ // end-change
86
+ {
87
+ contacts {
88
+ id
89
+ ...ContactRowFragment
90
+ }
91
+ }
92
+ `;
93
+ ```
94
+
95
+ ### Step 2 — Pass the fragment argument as a field argument
96
+
97
+ Pass the fragment argument in as an argument to the `contacts` field.
98
+
99
+ ```
100
+ const ContactsListFragment = graphql`
101
+ fragment ContactsListFragment on Viewer
102
+ @argumentDefinitions(
103
+ search: {type: "String", defaultValue: null}
104
+ )
105
+ {
106
+ // change-line
107
+ contacts(search: $search) {
108
+ id
109
+ ...ContactRowFragment
110
+ }
111
+ }
112
+ `;
113
+ ```
114
+
115
+ Remember, the first `search` here is the name of the argument to `contacts`, while the second `$search` is the variable created by our fragment argument.
116
+
117
+ ### Step 3 — Add the @refetchable directive
118
+
119
+ Next we'll add a `@refetchable` directive. This tells Relay to generate the extra query for refetching it. You have to specify the name of the generated query — it's a good idea to base it on the name of the fragment.
120
+
121
+ ```
122
+ const ContactsListFragment = graphql`
123
+ fragment ContactsListFragment on Viewer
124
+ // change-line
125
+ @refetchable(queryName: "ContactsListRefetchQuery")
126
+ @argumentDefinitions(
127
+ search: {type: "String", defaultValue: null}
128
+ )
129
+ {
130
+ // ...
131
+ }
132
+ `;
133
+ ```
134
+
135
+ ### Step 4 — Add the search input
136
+
137
+ Now we need to actually hook this up to our UI. Take a look at the `ContactsList` component:
138
+
139
+ ```
140
+ export default function ContactsList({ viewer }: Props) {
141
+ const data = useFragment(ContactsListFragment, viewer);
142
+ return (
143
+ <Card dim={true}>
144
+ <h3>Contacts</h3>
145
+ {data.contacts.map(contact =>
146
+ <ContactRow key={contact.id} contact={contact} />
147
+ )}
148
+ </Card>
149
+ );
150
+ }
151
+ ```
152
+
153
+ First we need to add a search field.
154
+
155
+ ```
156
+ // change-line
157
+ import SearchInput from './SearchInput';
158
+
159
+ // change-line
160
+ const {useState} = React;
161
+
162
+ function ContactsList({viewer}) {
163
+ const data = useFragment(ContactsListFragment, viewer);
164
+ // change-line
165
+ const [searchString, setSearchString] = useState('');
166
+ // change
167
+ const onSearchStringChanged = (value: string) => {
168
+ setSearchString(value);
169
+ };
170
+ // end-change
171
+ return (
172
+ <Card dim={true}>
173
+ <h3>Contacts</h3>
174
+ // change
175
+ <SearchInput
176
+ value={searchString}
177
+ onChange={onSearchStringChanged}
178
+ />
179
+ // end-change
180
+ {data.contacts.map(contact =>
181
+ <ContactRow key={contact.id} contact={contact} />
182
+ )}
183
+ </Card>
184
+ );
185
+ }
186
+ ```
187
+
188
+ ### Step 5 — Call useRefetchableFragment
189
+
190
+ Now to refetch the fragment when the string changes, we change `useFragment` to `useRefetchableFragment`. This hook returns a `refetch` function which will refetch the fragment with new variables which we provide as an argument.
191
+
192
+ ```
193
+ // change-line
194
+ import {useRefetchableFragment} from 'react-relay';
195
+
196
+ function ContactsList({viewer}) {
197
+ // change-line
198
+ const [data, refetch] = useRefetchableFragment(ContactsListFragment, viewer);
199
+ const [searchString, setSearchString] = useState('');
200
+ const onSearchStringChanged = (value) => {
201
+ setSearchString(value);
202
+ // change-line
203
+ refetch({search: value});
204
+ };
205
+ return (
206
+ // ...
207
+ );
208
+ }
209
+ ```
210
+
211
+ You’ll notice that Relay gives us a callback for refetching, rather than accepting the new state variables as an argument to the hook and refetching when it is re-rendered with a different value. This means that the fetch begins as soon as the event takes place, saving some time versus waiting until React finishes re-rendering — the same principle we saw before with preloaded queries. It also gives us more control, for example if we wanted to debounce the refetch.
212
+
213
+ ### Step 6 — Control loading with useTransition
214
+
215
+ At this point, when the fragment is refreshed, Relay uses Suspense while the new data is loading, so the entire component is replaced with a spinner! This makes the UI fairly unusable. We would rather just keep the current data on screen until the new data is available.
216
+
217
+ The way Suspense normally works is this: When a component is missing data that it needs to render (as our component does after we refetch), it tells React to wait. When this happens, React finds the nearest Suspense component in the tree. It then replaces everything under that component with a "fallback" loading indicator.
218
+
219
+ ![Component needs data](/img/docs/tutorial/refetchable-suspense-1-data-needed.png)
220
+ ![React finds the nearest Suspense point](/img/docs/tutorial/refetechable-suspense-2-nearest-suspense-point.png)
221
+ ![Renders a fallback at that point until the data is available](/img/docs/tutorial/refetchable-suspense-3-fallback.png)
222
+
223
+ This makes sense when initially loading a screen, but in this instance there's no reason to hide the existing UI and replace it with a spinner. While React is waiting, it can simply continue showing what's already there.
224
+
225
+ To achieve this, we can mark the refetch as a *transition*. Transitions are React state updates that do not need to be immediately responded to — React can wait until the data is available.
226
+
227
+ Transitions are marked by wrapping the state change in a call to a function provided by the `useTransition` hook. This is what the code will look like:
228
+
229
+ ```
230
+ // change-line
231
+ const {useState, useTransition} = React;
232
+
233
+ function ContactsList({viewer}) {
234
+ // change-line
235
+ const [isPending, startTransition] = useTransition();
236
+ const [searchString, setSearchString] = useState('');
237
+ const [data, refetch] = useRefetchableFragment(ContactsListFragment, viewer);
238
+ const onSearchStringChanged = (value) => {
239
+ setSearchString(value);
240
+ // change
241
+ startTransition(() => {
242
+ refetch({search: value});
243
+ });
244
+ // end-change
245
+ };
246
+ return (
247
+ <Card dim={true}>
248
+ <h3>Contacts</h3>
249
+ <SearchInput
250
+ value={searchString}
251
+ onChange={onSearchStringChanged}
252
+ // change-line
253
+ isPending={isPending}
254
+ />
255
+ {data.contacts.map(contact =>
256
+ <ContactRow key={contact.id} contact={contact} />
257
+ )}
258
+ </Card>
259
+ );
260
+ }
261
+ ```
262
+
263
+ While React is waiting for the new data, instead of using a Suspense fallback, React re-renders the component with the `isPending` flag set to true.
264
+
265
+ We simply pass the `isPending` flag to `SearchInput` (which causes it to show a spinner) while the refetch is happening. Meanwhile, by placing `setSearchString` outside of the transition but `refetch` within it, we tell React to immediately update the search input.
266
+
267
+ We should now be able to search the contacts list with a nice user experience, showing a spinner but keeping the previous data visible while loading.
268
+
269
+ ![Search input goes from spinner to filtered list](/img/docs/tutorial/refetchable-transition-search.png)
270
+
271
+ <details>
272
+ <summary>Deep dive: What fragments can be refetched?</summary>
273
+
274
+ To refetch fragments, Relay has to know how to generate a query that lets it refetch just the information from the fragment. That’s only possible for fragments that meet certain requirements.
275
+
276
+ You might imagine that we could, if nothing else, re-run the original query that the fragment was spread into. However, GraphQL doesn’t guarantee that the same query will return the same results at different times. For instance, imagine you had a GraphQL field that returned the top trending posts across the site:
277
+
278
+ ```
279
+ query MyQuery {
280
+ topTrendingPosts {
281
+ title
282
+ summary
283
+ date
284
+ poster {
285
+ ...PosterFragment
286
+ }
287
+ }
288
+ }
289
+ ```
290
+
291
+ If you wanted to refresh just `PosterFragment` from this query, it wouldn’t work to construct a query like this:
292
+
293
+ ```
294
+ query MyQuery {
295
+ topTrendingPosts {
296
+ poster {
297
+ ...PosterFragment
298
+ }
299
+ }
300
+ }
301
+ ```
302
+
303
+ ... because the top trending post could be a different post by the time you refresh it!
304
+
305
+ Relay needs a way of identifying the specific node in the graph that the fragment ends up on, even if it can no longer be reached by the same path that the original query uses. If the node has a unique and stable ID, then we can just have a convention for querying for “the graph node with some specific ID” like so:
306
+
307
+ ```
308
+ query RefetchQuery {
309
+ node(id: "abcdef") {
310
+ ...PosterFragment
311
+ }
312
+ }
313
+ ```
314
+
315
+ In fact, this is exactly the convention that Relay uses. It expects your server to implement a top-level field called `node` that takes an ID and gives you the graph node with that ID. (We saw `node` earlier with the hovercard example — there it was used to fetch a specific person given their ID using a secondary query.)
316
+
317
+ Not every graph node has a stable ID — some are ephemeral. To be used with `node`, your schema has to declare that its type implements an interface called `Node`:
318
+
319
+ ```
320
+ type Person implements Node {
321
+ id: ID!
322
+ ...
323
+ }
324
+ ```
325
+
326
+ The `Node` interface simply says it has an ID, but more importantly indicates by convention that that ID is stable and unique:
327
+
328
+ ```
329
+ interface Node {
330
+ id: ID!
331
+ }
332
+ ```
333
+
334
+ import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
335
+
336
+ Besides fragments on types that implement `Node`, you can also refetch fragments that are on `Viewer` (since the viewer is assumed to be stable throughout a session) and that are at the top level of a query (since there’s no field above them that could change identity).
337
+
338
+ <FbInternalOnly>
339
+
340
+ Meta only: Ents marked with <a href="https://fb.workplace.com/groups/graphql.fyi/permalink/1539541276187011/" target="_blank">GraphQLFetchable</a> can also be refetched.
341
+
342
+ </FbInternalOnly>
343
+
344
+ </details>
345
+
346
+ * * *
347
+
348
+ ## Summary
349
+
350
+ Refetchable fragments let us efficiently update specific parts of the UI in response to user input, while initializing them as part of the same query that we use for the entire screen.
351
+
352
+ Relay's pagination features are built on refetchable fragments, too. We'll explore those next.
@@ -30,12 +30,12 @@ function assertInternalActorIdentifier(actorIdentifier: ActorIdentifier): void {
30
30
  }
31
31
 
32
32
  module.exports = {
33
+ INTERNAL_ACTOR_IDENTIFIER_DO_NOT_USE,
33
34
  assertInternalActorIdentifier,
34
35
  getActorIdentifier(actorID: string): ActorIdentifier {
35
- return (actorID: ActorIdentifier);
36
+ return actorID as ActorIdentifier;
36
37
  },
37
38
  getDefaultActorIdentifier(): ActorIdentifier {
38
39
  throw new Error('Not Implemented');
39
40
  },
40
- INTERNAL_ACTOR_IDENTIFIER_DO_NOT_USE,
41
41
  };
@@ -46,7 +46,7 @@ const RelayOperationTracker = require('../store/RelayOperationTracker');
46
46
  const RelayPublishQueue = require('../store/RelayPublishQueue');
47
47
  const registerEnvironmentWithDevTools = require('../util/registerEnvironmentWithDevTools');
48
48
 
49
- export type ActorSpecificEnvironmentConfig = $ReadOnly<{
49
+ export type ActorSpecificEnvironmentConfig = Readonly<{
50
50
  actorIdentifier: ActorIdentifier,
51
51
  configName: ?string,
52
52
  defaultRenderPolicy: RenderPolicy,
@@ -56,7 +56,7 @@ export type ActorSpecificEnvironmentConfig = $ReadOnly<{
56
56
  network: INetwork,
57
57
  relayFieldLogger: RelayFieldLogger,
58
58
  store: Store,
59
- missingFieldHandlers: $ReadOnlyArray<MissingFieldHandler>,
59
+ missingFieldHandlers: ReadonlyArray<MissingFieldHandler>,
60
60
  }>;
61
61
 
62
62
  class ActorSpecificEnvironment implements IActorEnvironment {
@@ -69,7 +69,7 @@ class ActorSpecificEnvironment implements IActorEnvironment {
69
69
  +actorIdentifier: ActorIdentifier;
70
70
  +configName: ?string;
71
71
  +multiActorEnvironment: IMultiActorEnvironment;
72
- +options: mixed;
72
+ +options: unknown;
73
73
  relayFieldLogger: RelayFieldLogger;
74
74
 
75
75
  constructor(config: ActorSpecificEnvironmentConfig) {
@@ -101,7 +101,7 @@ class ActorSpecificEnvironment implements IActorEnvironment {
101
101
 
102
102
  if (__DEV__) {
103
103
  const {inspect} = require('../store/StoreInspector');
104
- (this: $FlowFixMe).DEBUG_inspect = (dataID: ?string) =>
104
+ (this as $FlowFixMe).DEBUG_inspect = (dataID: ?string) =>
105
105
  inspect(this, dataID);
106
106
  }
107
107
 
@@ -118,7 +118,7 @@ class ActorSpecificEnvironment implements IActorEnvironment {
118
118
  return this._defaultRenderPolicy;
119
119
  }
120
120
 
121
- applyMutation<TMutation: MutationParameters>(
121
+ applyMutation<TMutation extends MutationParameters>(
122
122
  optimisticConfig: OptimisticResponseConfig<TMutation>,
123
123
  ): Disposable {
124
124
  return this.multiActorEnvironment.applyMutation(this, optimisticConfig);
@@ -202,14 +202,14 @@ class ActorSpecificEnvironment implements IActorEnvironment {
202
202
  return this.multiActorEnvironment.execute(this, config);
203
203
  }
204
204
 
205
- executeSubscription<TMutation: MutationParameters>(config: {
205
+ executeSubscription<TMutation extends MutationParameters>(config: {
206
206
  operation: OperationDescriptor,
207
207
  updater?: ?SelectorStoreUpdater<TMutation['response']>,
208
208
  }): RelayObservable<GraphQLResponse> {
209
209
  return this.multiActorEnvironment.executeSubscription(this, config);
210
210
  }
211
211
 
212
- executeMutation<TMutation: MutationParameters>(
212
+ executeMutation<TMutation extends MutationParameters>(
213
213
  options: ExecuteMutationConfig<TMutation>,
214
214
  ): RelayObservable<GraphQLResponse> {
215
215
  return this.multiActorEnvironment.executeMutation(this, options);
@@ -17,7 +17,7 @@ import type {ActorIdentifier} from './ActorIdentifier';
17
17
 
18
18
  const {getActorIdentifier} = require('./ActorIdentifier');
19
19
 
20
- function getActorIdentifierFromPayload(payload: mixed): ?ActorIdentifier {
20
+ function getActorIdentifierFromPayload(payload: unknown): ?ActorIdentifier {
21
21
  if (
22
22
  payload != null &&
23
23
  typeof payload === 'object' &&
@@ -55,7 +55,7 @@ const RelayModernStore = require('../store/RelayModernStore');
55
55
  const RelayRecordSource = require('../store/RelayRecordSource');
56
56
  const ActorSpecificEnvironment = require('./ActorSpecificEnvironment');
57
57
 
58
- export type MultiActorEnvironmentConfig = $ReadOnly<{
58
+ export type MultiActorEnvironmentConfig = Readonly<{
59
59
  createConfigNameForActor?: ?(actorIdentifier: ActorIdentifier) => string,
60
60
  createNetworkForActor: (actorIdentifier: ActorIdentifier) => INetwork,
61
61
  createStoreForActor?: ?(actorIdentifier: ActorIdentifier) => Store,
@@ -64,13 +64,14 @@ export type MultiActorEnvironmentConfig = $ReadOnly<{
64
64
  handlerProvider?: HandlerProvider,
65
65
  isServer?: ?boolean,
66
66
  logFn?: ?LogFunction,
67
- missingFieldHandlers?: ?$ReadOnlyArray<MissingFieldHandler>,
67
+ missingFieldHandlers?: ?ReadonlyArray<MissingFieldHandler>,
68
68
  normalizeResponse?: NormalizeResponseFunction,
69
69
  operationLoader?: ?OperationLoader,
70
70
  relayFieldLogger?: ?RelayFieldLogger,
71
71
  scheduler?: ?TaskScheduler,
72
72
  shouldProcessClientComponents?: ?boolean,
73
73
  treatMissingFieldsAsNull?: boolean,
74
+ deferDeduplicatedFields?: boolean,
74
75
  }>;
75
76
 
76
77
  class MultiActorEnvironment implements IMultiActorEnvironment {
@@ -83,7 +84,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
83
84
  +_handlerProvider: HandlerProvider;
84
85
  +_isServer: boolean;
85
86
  +_logFn: LogFunction;
86
- +_missingFieldHandlers: $ReadOnlyArray<MissingFieldHandler>;
87
+ +_missingFieldHandlers: ReadonlyArray<MissingFieldHandler>;
87
88
  +_normalizeResponse: NormalizeResponseFunction;
88
89
  +_operationExecutions: Map<string, ActiveState>;
89
90
  +_operationLoader: ?OperationLoader;
@@ -91,6 +92,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
91
92
  +_scheduler: ?TaskScheduler;
92
93
  +_shouldProcessClientComponents: ?boolean;
93
94
  +_treatMissingFieldsAsNull: boolean;
95
+ +_deferDeduplicatedFields: boolean;
94
96
 
95
97
  constructor(config: MultiActorEnvironmentConfig) {
96
98
  this._actorEnvironments = new Map();
@@ -106,6 +108,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
106
108
  this._relayFieldLogger = config.relayFieldLogger ?? defaultRelayFieldLogger;
107
109
  this._shouldProcessClientComponents = config.shouldProcessClientComponents;
108
110
  this._treatMissingFieldsAsNull = config.treatMissingFieldsAsNull ?? false;
111
+ this._deferDeduplicatedFields = config.deferDeduplicatedFields ?? false;
109
112
  this._isServer = config.isServer ?? false;
110
113
  this._missingFieldHandlers = config.missingFieldHandlers ?? [];
111
114
  this._createStoreForActor = config.createStoreForActor;
@@ -175,7 +178,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
175
178
  _checkSelectorAndHandleMissingFields(
176
179
  actorEnvironment: IActorEnvironment,
177
180
  operation: OperationDescriptor,
178
- handlers: $ReadOnlyArray<MissingFieldHandler>,
181
+ handlers: ReadonlyArray<MissingFieldHandler>,
179
182
  ): OperationAvailability {
180
183
  const targets: Map<ActorIdentifier, MutableRecordSource> = new Map([
181
184
  [actorEnvironment.actorIdentifier, RelayRecordSource.create()],
@@ -267,7 +270,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
267
270
  });
268
271
  }
269
272
 
270
- applyMutation<TMutation: MutationParameters>(
273
+ applyMutation<TMutation extends MutationParameters>(
271
274
  actorEnvironment: IActorEnvironment,
272
275
  optimisticConfig: OptimisticResponseConfig<TMutation>,
273
276
  ): Disposable {
@@ -341,7 +344,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
341
344
  });
342
345
  }
343
346
 
344
- executeSubscription<TMutation: MutationParameters>(
347
+ executeSubscription<TMutation extends MutationParameters>(
345
348
  actorEnvironment: IActorEnvironment,
346
349
  {
347
350
  operation,
@@ -368,7 +371,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
368
371
  });
369
372
  }
370
373
 
371
- executeMutation<TMutation: MutationParameters>(
374
+ executeMutation<TMutation extends MutationParameters>(
372
375
  actorEnvironment: IActorEnvironment,
373
376
  {
374
377
  operation,
@@ -432,7 +435,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
432
435
  return this._isServer;
433
436
  }
434
437
 
435
- _execute<TMutation: MutationParameters>(
438
+ _execute<TMutation extends MutationParameters>(
436
439
  actorEnvironment: IActorEnvironment,
437
440
  {
438
441
  createSource,
@@ -471,6 +474,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
471
474
  return this.forActor(actorIdentifier).getStore();
472
475
  },
473
476
  treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
477
+ deferDeduplicatedFields: this._deferDeduplicatedFields,
474
478
  updater,
475
479
  log: this._logFn,
476
480
  normalizeResponse: this._normalizeResponse,
@@ -140,7 +140,7 @@ export interface IMultiActorEnvironment {
140
140
  * Apply an optimistic mutation response and/or updater. The mutation can be
141
141
  * reverted by calling `dispose()` on the returned value.
142
142
  */
143
- applyMutation<TMutation: MutationParameters>(
143
+ applyMutation<TMutation extends MutationParameters>(
144
144
  actorEnvironment: IActorEnvironment,
145
145
  optimisticConfig: OptimisticResponseConfig<TMutation>,
146
146
  ): Disposable;
@@ -203,7 +203,7 @@ export interface IMultiActorEnvironment {
203
203
  * Note: Observables are lazy, so calling this method will do nothing until
204
204
  * the result is subscribed to: environment.executeSubscription({...}).subscribe({...}).
205
205
  */
206
- executeSubscription<TMutation: MutationParameters>(
206
+ executeSubscription<TMutation extends MutationParameters>(
207
207
  actorEnvironment: IActorEnvironment,
208
208
  config: {
209
209
  operation: OperationDescriptor,
@@ -221,7 +221,7 @@ export interface IMultiActorEnvironment {
221
221
  * the result is subscribed to:
222
222
  * environment.executeMutation({...}).subscribe({...}).
223
223
  */
224
- executeMutation<TMutation: MutationParameters>(
224
+ executeMutation<TMutation extends MutationParameters>(
225
225
  actorEnvironment: IActorEnvironment,
226
226
  config: ExecuteMutationConfig<TMutation>,
227
227
  ): RelayObservable<GraphQLResponse>;
@@ -27,13 +27,13 @@ const MutationTypes = Object.freeze({
27
27
  RANGE_DELETE: 'RANGE_DELETE',
28
28
  NODE_DELETE: 'NODE_DELETE',
29
29
  });
30
- export type MutationType = $Values<typeof MutationTypes>;
30
+ export type MutationType = Values<typeof MutationTypes>;
31
31
 
32
32
  const RangeOperations = Object.freeze({
33
33
  APPEND: 'append',
34
34
  PREPEND: 'prepend',
35
35
  });
36
- export type RangeOperation = $Values<typeof RangeOperations>;
36
+ export type RangeOperation = Values<typeof RangeOperations>;
37
37
 
38
38
  type RangeBehaviorsFunction = (connectionArgs: {
39
39
  [name: string]: $FlowFixMe,
@@ -82,7 +82,7 @@ export type DeclarativeMutationConfig =
82
82
  | RangeDeleteConfig
83
83
  | NodeDeleteConfig;
84
84
 
85
- function convert<TMutation: MutationParameters>(
85
+ function convert<TMutation extends MutationParameters>(
86
86
  configs: Array<DeclarativeMutationConfig>,
87
87
  request: ConcreteRequest,
88
88
  optimisticUpdater?: ?SelectorStoreUpdater<TMutation['response']>,
@@ -145,13 +145,13 @@ function convert<TMutation: MutationParameters>(
145
145
  function nodeDelete(
146
146
  config: NodeDeleteConfig,
147
147
  request: ConcreteRequest,
148
- ): ?SelectorStoreUpdater<mixed> {
148
+ ): ?SelectorStoreUpdater<unknown> {
149
149
  const {deletedIDFieldName} = config;
150
150
  const rootField = getRootField(request);
151
151
  if (!rootField) {
152
152
  return null;
153
153
  }
154
- return (store: RecordSourceSelectorProxy, data: ?mixed) => {
154
+ return (store: RecordSourceSelectorProxy, data: ?unknown) => {
155
155
  const payload = store.getRootField(rootField);
156
156
  if (!payload) {
157
157
  return;
@@ -169,7 +169,7 @@ function nodeDelete(
169
169
  function rangeAdd(
170
170
  config: RangeAddConfig,
171
171
  request: ConcreteRequest,
172
- ): ?SelectorStoreUpdater<mixed> {
172
+ ): ?SelectorStoreUpdater<unknown> {
173
173
  const {parentID, connectionInfo, edgeName} = config;
174
174
  if (!parentID) {
175
175
  warning(
@@ -183,7 +183,7 @@ function rangeAdd(
183
183
  if (!connectionInfo || !rootField) {
184
184
  return null;
185
185
  }
186
- return (store: RecordSourceSelectorProxy, data: ?mixed) => {
186
+ return (store: RecordSourceSelectorProxy, data: ?unknown) => {
187
187
  const parent = store.get(parentID);
188
188
  if (!parent) {
189
189
  return;
@@ -237,7 +237,7 @@ function rangeAdd(
237
237
  function rangeDelete(
238
238
  config: RangeDeleteConfig,
239
239
  request: ConcreteRequest,
240
- ): ?SelectorStoreUpdater<mixed> {
240
+ ): ?SelectorStoreUpdater<unknown> {
241
241
  const {parentID, connectionKeys, pathToConnection, deletedIDFieldName} =
242
242
  config;
243
243
  if (!parentID) {
@@ -252,7 +252,7 @@ function rangeDelete(
252
252
  if (!rootField) {
253
253
  return null;
254
254
  }
255
- return (store: RecordSourceSelectorProxy, data: ?mixed) => {
255
+ return (store: RecordSourceSelectorProxy, data: ?unknown) => {
256
256
  if (!data) {
257
257
  return;
258
258
  }