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,157 @@
1
+ ---
2
+ id: advanced-pagination
3
+ title: Advanced Pagination
4
+ slug: /guided-tour/list-data/advanced-pagination/
5
+ description: Relay guide for advanced pagination
6
+ keywords:
7
+ - pagination
8
+ - usePaginationFragment
9
+ - prefetching
10
+ ---
11
+
12
+ import DocsRating from '@site/src/core/DocsRating';
13
+ import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
14
+
15
+ In this section we're going to cover how to implement more advanced pagination use cases than the default cases covered by `usePaginationFragment`.
16
+
17
+
18
+ ## Pagination Over Multiple Connections
19
+
20
+ If you need to paginate over multiple connections within the same component, you can use `usePaginationFragment` multiple times:
21
+
22
+ ```js
23
+ import type {CombinedFriendsListComponent_user$key} from 'CombinedFriendsListComponent_user.graphql';
24
+ import type {CombinedFriendsListComponent_viewer$key} from 'CombinedFriendsListComponent_viewer.graphql';
25
+
26
+ const React = require('React');
27
+
28
+ const {graphql, usePaginationFragment} = require('react-relay');
29
+
30
+ type Props = {
31
+ user: CombinedFriendsListComponent_user$key,
32
+ viewer: CombinedFriendsListComponent_viewer$key,
33
+ };
34
+
35
+ function CombinedFriendsListComponent(props: Props) {
36
+
37
+ const {data: userData, ...userPagination} = usePaginationFragment(
38
+ graphql`
39
+ fragment CombinedFriendsListComponent_user on User {
40
+ name
41
+ friends
42
+ @connection(
43
+ key: "CombinedFriendsListComponent_user_friends_connection"
44
+ ) {
45
+ edges {
46
+ node {
47
+ name
48
+ age
49
+ }
50
+ }
51
+ }
52
+ }
53
+ `,
54
+ props.user,
55
+ );
56
+
57
+ const {data: viewerData, ...viewerPagination} = usePaginationFragment(
58
+ graphql`
59
+ fragment CombinedFriendsListComponent_user on Viewer {
60
+ actor {
61
+ ... on User {
62
+ name
63
+ friends
64
+ @connection(
65
+ key: "CombinedFriendsListComponent_viewer_friends_connection"
66
+ ) {
67
+ edges {
68
+ node {
69
+ name
70
+ age
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ `,
78
+ props.viewer,
79
+ );
80
+
81
+ return (...);
82
+ }
83
+ ```
84
+
85
+ However, we recommend trying to keep a single connection per component, to keep the components easier to follow.
86
+
87
+
88
+
89
+ ## Bi-directional Pagination
90
+
91
+ In the [Pagination](../pagination/) section we covered how to use `usePaginationFragment` to paginate in a single *"forward"* direction. However, connections also allow paginating in the opposite *"backward"* direction. The meaning of *"forward"* and *"backward"* directions will depend on how the items in the connection are sorted, for example *"forward"* could mean more recent*, and "backward"* could mean less recent.
92
+
93
+ Regardless of the semantic meaning of the direction, Relay also provides the same APIs to paginate in the opposite direction, using `usePaginationFragment`, as long as the `before` and `last` connection arguments are also used along with `after` and `first`:
94
+
95
+ ```js
96
+ import type {FriendsListComponent_user$key} from 'FriendsListComponent_user.graphql';
97
+
98
+ const React = require('React');
99
+ const {Suspense} = require('React');
100
+
101
+ const {graphql, usePaginationFragment} = require('react-relay');
102
+
103
+ type Props = {
104
+ userRef: FriendsListComponent_user$key,
105
+ };
106
+
107
+ function FriendsListComponent(props: Props) {
108
+ const {
109
+ data,
110
+ loadPrevious,
111
+ hasPrevious,
112
+ // ... forward pagination values
113
+ } = usePaginationFragment(
114
+ graphql`
115
+ fragment FriendsListComponent_user on User {
116
+ name
117
+ friends(after: $after, before: $before, first: $first, last: $last)
118
+ @connection(key: "FriendsListComponent_user_friends_connection") {
119
+ edges {
120
+ node {
121
+ name
122
+ age
123
+ }
124
+ }
125
+ }
126
+ }
127
+ `,
128
+ userRef,
129
+ );
130
+
131
+ return (
132
+ <>
133
+ <h1>Friends of {data.name}:</h1>
134
+ <List items={data.friends?.edges.map(edge => edge.node)}>
135
+ {node => {
136
+ return (
137
+ <div>
138
+ {node.name} - {node.age}
139
+ </div>
140
+ );
141
+ }}
142
+ </List>
143
+
144
+ {hasPrevious ? (
145
+ <Button onClick={() => loadPrevious(10)}>
146
+ Load more friends
147
+ </Button>
148
+ ) : null}
149
+
150
+ {/* Forward pagination controls can go simultaneously here */}
151
+ </>
152
+ );
153
+ }
154
+ ```
155
+
156
+ * The APIs for both *"forward"* and *"backward"* are exactly the same, they're only named differently. When paginating forward, then the `after` and `first` connection arguments will be used, when paginating backward, the `before` and `last` connection arguments will be used.
157
+ * Note that the primitives for both *"forward"* and *"backward"* pagination are exposed from a single call to `usePaginationFragment`, so both *"forward"* and *"backward"* pagination can be performed simultaneously in the same component.
@@ -0,0 +1,81 @@
1
+ ---
2
+ id: connections
3
+ title: Why Connections?
4
+ slug: /guided-tour/list-data/connections/
5
+ description: Relay guide for connections
6
+ keywords:
7
+ - pagination
8
+ - connections
9
+ ---
10
+
11
+ import DocsRating from '@site/src/core/DocsRating';
12
+
13
+ # Why Connections?
14
+
15
+ :::info
16
+ For a video format of how the connection spec was derived, check out [Sabrina Wasserman's GraphQL talk](../../guides/graphql-server-specification.mdx#graphql-conf-talk)!
17
+ :::
18
+
19
+ Relay does a lot of the work for you when handling paginated collections of items. But to do that, it relies on a specific convention for how those collections are modeled in your schema. This convention is powerful and flexible, and comes out of experience building many products with collections of items. Let’s step through the design process for this schema convention so that we can understand why it works this way.
20
+
21
+ There are three important points to understand:
22
+
23
+ * You may need to model data about an item's inclusion in the collection, such as in a list of friends where you may wish to model the date you friended that person. We handle this by creating nodes that represent the edges, the relationship between the item in the collection and the collection itself.
24
+ * The page itself has properties, such as whether or not there is a next page available. We handle this with a node that represents the current page info.
25
+ * Pagination is done by *cursors* — opaque symbols that point to the next page of results — rather than offsets.
26
+
27
+ Imagine we want to show a list of the user’s friends. At a high level, we imagine a graph where the viewer and their friends are each nodes. From the viewer to each friend node is an edge, and the edge itself has properties.
28
+
29
+ ![Conceptual graph with properties on its edges](/img/docs/tutorial/connections-conceptual-graph.png)
30
+
31
+ Now let’s try to model this situation using GraphQL.
32
+
33
+ In GraphQL, only nodes can have properties, not edges. So the first thing we’ll do is represent the conceptual edge from you to your friend with its very own node.
34
+
35
+ ![Edge properties modeled using nodes that represent the edges](/img/docs/tutorial/connections-edge-nodes.png)
36
+
37
+ Now the properties of the edge are represented by a new type of node called a “`FriendsEdge`”.
38
+
39
+ The GraphQL to query this would look like this:
40
+
41
+ ```
42
+ // XXX example only, not final code
43
+ fragment FriendsFragment1 on Viewer {
44
+ friends {
45
+ since // a property of the edge
46
+ node {
47
+ name // a property of the friend itself
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Now we have a good place in the GraphQL schema to put edge-specific information such as the date when the edge was created (that is, the date you friended that person).
54
+
55
+ * * *
56
+
57
+ Now consider what we would need to model in our schema in order to support pagination and infinite scrolling.
58
+
59
+ * The client must be able to specify how large of a page it wants.
60
+ * The client must be informed as to whether any more pages are available, so that it can enable or disable the ‘next page’ button (or, for infinite scrolling, can stop making further requests).
61
+ * The client must be able to ask for the next page after the one it already has.
62
+
63
+ How can we use the features of GraphQL to do these things? Specifying the page size is done with field arguments. In other words, instead of just `friends` the query will say `friends(first: 3)`, passing the page size as an argument to the `friends` field.
64
+
65
+ For the server to say whether there is a next page or not, we need to introduce a node in the graph that has information about the *list of friends itself,* just like we are introducing a node for each edge to store information about the edge itself. This new node is called a *Connection*.
66
+
67
+ The Connection node represents the connection itself between you and your friends. Metadata about the connection is stored there — for example, it could have a `totalCount` field that says how many friends you have. In addition, it always has two fields which represent the *current* page: a `pageInfo` field with metadata about the current page, such as whether there is another page available — and an `edges` field that points to the edges we saw before:
68
+
69
+ ![The full connection model with page info and edges](/img/docs/tutorial/connections-full-model.png)
70
+
71
+ Finally, we need a way to request the next page of results. You’ll notice in the above diagram that the `PageInfo` node has a field called `lastCursor`. This is an opaque token provided by the server that represents the position in the list of the last edge that we were given (the friend “Charmaine”). We can then pass this cursor back to the server in order to retrieve the next page.
72
+
73
+ By passing the `lastCursor` value back to the server as an argument to the `friends` field, we can ask the server for friends that are *after* the ones we’ve already retrieved:
74
+
75
+ ![After fetching the next page of results](/img/docs/tutorial/connections-full-model-next-page.png)
76
+
77
+ This overall scheme for modeling paginated lists is specified in detail in the [GraphQL Cursor Connections Spec](https://relay.dev/graphql/connections.htm). It is flexible for many different applications, and although Relay relies on this convention to handle pagination automatically, designing your schema this way is a good idea whether or not you use Relay.
78
+
79
+ Now that we've stepped through the underlying model for Connections, let’s turn our attention to actually using it to implement Comments for our Newsfeed stories.
80
+
81
+ <DocsRating />
@@ -0,0 +1,193 @@
1
+ ---
2
+ id: pagination
3
+ title: Pagination with Connections
4
+ slug: /guided-tour/list-data/pagination/
5
+ description: Relay guide to connections and pagination
6
+ keywords:
7
+ - pagination
8
+ - connections
9
+ - usePaginationFragment
10
+ ---
11
+
12
+ import DocsRating from '@site/src/core/DocsRating';
13
+
14
+ # Pagination with Connections
15
+
16
+ In order to paginate through lists of data (load only discrete slices of list data), Relay introduces an abstraction known as a "connection". Connections allow you to request discrete slices of data from the server as well as information about the list itself (e.g. how to get the next page of data). For more information about the design of connections, see [Why Connections?](./connections.mdx).
17
+
18
+ A query with a connection includes:
19
+ - the *connection* itself, a field that returns the `edges` and `pageInfo`. It includes a `first` argument to indicate the page size and an `after` argument to indicate where the beginning of the list is.
20
+ - the *edge*, which contains the `cursor` (a bookmark of where you are in the list) and the `node` (the actual record the connection is paginating over).
21
+ - the *pageInfo*, which contains information about getting more edges (`hasPreviousPage`, `hasNextPage`, `startCursor`, and `endCursor`).
22
+
23
+ A typical query fragment (for fetching a list of comments) may look like:
24
+ ```
25
+ const StoryCommentsSectionFragment = graphql`
26
+ fragment StoryCommentsSectionFragment on Story {
27
+ # the connection with arguments to specify which items to fetch
28
+ comments(after: $cursor, first: $count) {
29
+ edges { # the list of edges
30
+ node { # the Comment record itself
31
+ ...CommentFragment # the fields on Comment to fetch
32
+ }
33
+ cursor # an identifier of this Comment's place in the list
34
+ }
35
+ pageInfo { # the page info
36
+ hasNextPage # if another page of comments is available
37
+ }
38
+ }
39
+ }
40
+ `;
41
+ ```
42
+
43
+ :::info
44
+ Relay uses "cursors" to specify the beginning of a list. Each node is associated with a unique cursor, which can be passed to the connection to fetch the next page _after_ that cursor. This makes it easy to fetch consecutive pages of data.
45
+ :::
46
+
47
+ ## Connection Directives
48
+
49
+ Relay handles a lot of the pagination logic for you but to do so, connections must be augmented with directives containing some additional information.
50
+
51
+ ```
52
+ const StoryCommentsSectionFragment = graphql`
53
+ fragment StoryCommentsSectionFragment on Story
54
+ @argumentDefinitions(
55
+ cursor: { type: "String" }
56
+ count: { type: "Int", defaultValue: 3 }
57
+ )
58
+ @refetchable(queryName: "StoryCommentsSectionPaginationQuery") {
59
+ comments(after: $cursor, first: $count)
60
+ @connection(key: "StoryCommentsSectionFragment_comments") {
61
+ edges {
62
+ node {
63
+ ...CommentFragment
64
+ }
65
+ cursor
66
+ }
67
+ pageInfo {
68
+ hasNextPage
69
+ hasPreviousPage
70
+ startCursor
71
+ endCursor
72
+ }
73
+ }
74
+ }
75
+ `;
76
+ ```
77
+ Breaking this down:
78
+ - `@argumentDefinitions` is just defining the `$cursor` and `$count` variables as [fragment arguments](../../api-reference/graphql/graphql-directives.mdx#argumentdefinitions)
79
+ - `@refetchable` makes the fragment [refetchable](../../tutorial/refetchable-fragments.mdx) so that Relay can fetch it again with new arguments — usually, a new cursor for the `$cursor` argument.
80
+ - `@connection` is what Relay uses to tell _which field_ within the fragment represents the connection to paginate over. The `@connection` directive requires a `key` argument which must be a unique string — here formed from the fragment name and field name. This key is used when editing the connection’s contents via [mutations](../updating-data/updating-connections.mdx).
81
+
82
+ :::tip
83
+ Relay’s pagination features only work with fragments, not entire queries. This is usually fine as queries are generally issued at some high-level routing component, which would rarely be the same component that’s showing a paginated list. If you need to paginate something at the query level, refactor the connection field out into a fragment that is defined on the `Query` type.
84
+ :::
85
+
86
+ For a full specification of connections, see the [GraphQL Cursor Connections Spec](https://relay.dev/graphql/connections.htm).
87
+
88
+ ## The usePaginationFragment hook
89
+
90
+ Relay provides an easy way to use connections in React via the [`usePaginationFragment`](../../api-reference/hooks/use-pagination-fragment.mdx) hook. This hook acts similarly to the [`useFragment`](../../api-reference/hooks/use-fragment.mdx) hook but also provides additional callbacks for loading the next/previous page of data along with checking if those pages exist and loading indicators if a request is in flight. Using the `usePaginationFragment` hook also means you can omit the `pageInfo` and `cursor` fields from your fragment as Relay will automatically insert them and use the information to paginate via the hook. Taking the previous example with the `StoryCommentsSectionFragment`, the fragment can be reduced to:
91
+
92
+ ```
93
+ const StoryCommentsSectionFragment = graphql`
94
+ fragment StoryCommentsSectionFragment on Story
95
+ @argumentDefinitions(
96
+ cursor: { type: "String" }
97
+ count: { type: "Int", defaultValue: 3 }
98
+ )
99
+ @refetchable(queryName: "StoryCommentsSectionPaginationQuery") {
100
+ comments(after: $cursor, first: $count)
101
+ @connection(key: "StoryCommentsSectionFragment_comments") {
102
+ edges {
103
+ node {
104
+ ...CommentFragment
105
+ }
106
+ }
107
+ }
108
+ }
109
+ `;
110
+ ```
111
+
112
+ Here's an example of how the `usePaginationFragment` hook can be used to implement pagination in a React component:
113
+
114
+ ```
115
+ function StoryCommentsSection({story}) {
116
+ const {
117
+ data,
118
+ hasNext,
119
+ loadNext,
120
+ isLoadingNext,
121
+ } = usePaginationFragment(StoryCommentsSectionFragment, story);
122
+ return (
123
+ <>
124
+ {data.comments.edges.map(commentEdge =>
125
+ <Comment comment={commentEdge.node} />
126
+ )}
127
+ {hasNext && (
128
+ <LoadMoreCommentsButton
129
+ onClick={() => loadNext(3)}
130
+ disabled={isLoadingNext}
131
+ />
132
+ )}
133
+ {isLoadingNext && <SmallSpinner />}
134
+ </>
135
+ );
136
+ }
137
+ ```
138
+ In this example, the `story` prop passed to the `usePaginationFragment` is the same as the query reference passed to `useFragment`. Along with the fragment `data`, the hook in this example also returns `loadNext`, `hasNext`, and `isLoadingNext`.
139
+ - `hasNext` indicates whether another page of data exists.
140
+ - `loadNext` is a function that can be called to load the next page of data — for example when a user clicks on the `LoadMoreCommentsButton`. It takes an argument to indicate how many new items to fetch.
141
+ - In order to provide a better user experience, `isLoadingNext` can be used to show a loading indicator while the next page of comments is loading.
142
+
143
+ :::info
144
+ When the request to fetch the next items completes, the connection will be automatically updated and the component will re-render with the latest items in the connection. In our case, this means that the `comments` field will always contain *all* of the comments that we've fetched so far. By default, *Relay will automatically append new items to the connection upon completing a pagination request,* and will make them available to your fragment component*.* If you need a different behavior, check out the [Advanced Pagination Use Cases](./advanced-pagination.mdx) section.
145
+ :::
146
+
147
+ For the full information on the `usePaginationFragment` hook, check out the [API reference](../../api-reference/hooks/use-pagination-fragment.mdx).
148
+
149
+ ## React Loading States
150
+
151
+ React provides [suspense](https://react.dev/reference/react/Suspense) to allow a fallback while data is loading and [transitions](https://react.dev/reference/react/startTransition) to provide a better user experience when updating data. These can be used in conjunction with `usePaginationFragment`.
152
+
153
+ Taking the example above,
154
+ ```
155
+ function StoryCommentsSection({story}) {
156
+ const {
157
+ data,
158
+ hasNext,
159
+ loadNext,
160
+ isLoadingNext,
161
+ } = usePaginationFragment(StoryCommentsSectionFragment, story);
162
+ return (
163
+ <>
164
+ {data.comments.edges.map(commentEdge => {
165
+ return (
166
+ <Suspense fallback={<Glimmer />}>
167
+ <Comment comment={commentEdge.node} />
168
+ </Suspense>
169
+ );
170
+ })}
171
+ {hasNext && (
172
+ <LoadMoreCommentsButton
173
+ onClick={() => {
174
+ startTransition(() => {
175
+ loadNext(3)
176
+ });
177
+ }}
178
+ disabled={isLoadingNext}
179
+ />
180
+ )}
181
+ {isLoadingNext && <SmallSpinner />}
182
+ </>
183
+ );
184
+ }
185
+ ```
186
+
187
+ Calling `loadNext` may cause the component or new children components to suspend (as explained in [Loading States with Suspense](../rendering/loading-states.mdx)). This means that you'll need to make sure that there's a `Suspense` boundary wrapping this component from above and/or that you are using a transition in order to show the appropriate pending or loading state.
188
+
189
+ :::note
190
+ Since `loadNext` may cause the component to suspend, regardless of whether a transition is used to render a pending state (i.e. with a loading indicator from [useTransition](https://react.dev/reference/react/useTransition)), `startTransition` should always be used to schedule that update for the best user experience.
191
+ :::
192
+
193
+ <DocsRating />
@@ -0,0 +1,112 @@
1
+ ---
2
+ id: rendering-connections
3
+ title: Rendering Connections
4
+ slug: /guided-tour/list-data/rendering-connections/
5
+ description: Relay guide to rendering connections
6
+ keywords:
7
+ - pagination
8
+ - usePaginationFragment
9
+ - connection
10
+ ---
11
+
12
+ import DocsRating from '@site/src/core/DocsRating';
13
+ // @fb-only
14
+ // @fb-only
15
+ import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
16
+
17
+ In Relay, in order to display a list of data that is backed by a GraphQL connection, first you need to declare a fragment that queries for a connection:
18
+
19
+ ```js
20
+ const {graphql} = require('RelayModern');
21
+
22
+ const userFragment = graphql`
23
+ fragment UserFragment on User {
24
+ name
25
+ friends(after: $cursor, first: $count)
26
+ @connection(key: "UserFragment_friends") {
27
+ edges {
28
+ node {
29
+ ...FriendComponent
30
+ }
31
+ }
32
+ }
33
+ }
34
+ `;
35
+ ```
36
+
37
+ * In the example above, we're querying for the `friends` field, which is a connection; in other words, it adheres to the connection spec. Specifically, we can query the `edges` and `node`s in the connection; the `edges` usually contain information about the relationship between the entities, while the `node`s are the actual entities at the other end of the relationship; in this case, the `node`s are objects of type `User` representing the user's friends.
38
+ * In order to indicate to Relay that we want to perform pagination over this connection, we need to mark the field with the `@connection` directive. We must also provide a *static* unique identifier for this connection, known as the `key`. We recommend the following naming convention for the connection key: `<fragment_name>_<field_name>`.
39
+ * We will go into more detail later as to why it is necessary to mark the field as a `@connection` and give it a unique `key` in our [Updating Connections](../updating-connections/) section.
40
+
41
+
42
+ In order to render this fragment which queries for a connection, we can use the `usePaginationFragment` Hook:
43
+
44
+ <FbInternalOnly>
45
+ // @fb-only
46
+ </FbInternalOnly>
47
+
48
+ <OssOnly>
49
+
50
+ ```js
51
+ import type {FriendsListComponent_user$key} from 'FriendsList_user.graphql';
52
+
53
+ const React = require('React');
54
+ const {Suspense} = require('React');
55
+
56
+ const {graphql, usePaginationFragment} = require('react-relay');
57
+
58
+ type Props = {
59
+ user: FriendsListComponent_user$key,
60
+ };
61
+
62
+ function FriendsListComponent(props: Props) {
63
+ const {data} = usePaginationFragment(
64
+ graphql`
65
+ fragment FriendsListComponent_user on User
66
+ @refetchable(queryName: "FriendsListPaginationQuery") {
67
+ name
68
+ friends(first: $count, after: $cursor)
69
+ @connection(key: "FriendsList_user_friends") {
70
+ edges {
71
+ node {
72
+ ...FriendComponent
73
+ }
74
+ }
75
+ }
76
+ }
77
+ `,
78
+ props.user,
79
+ );
80
+
81
+
82
+ return (
83
+ <>
84
+ {data.name != null ? <h1>Friends of {data.name}:</h1> : null}
85
+
86
+ <div>
87
+ {/* Extract each friend from the resulting data */}
88
+ {(data.friends?.edges ?? []).map(edge => {
89
+ const node = edge.node;
90
+ return (
91
+ <Suspense fallback={<Glimmer />}>
92
+ <FriendComponent user={node} />
93
+ </Suspense>
94
+ );
95
+ })}
96
+ </div>
97
+ </>
98
+ );
99
+ }
100
+
101
+ module.exports = FriendsListComponent;
102
+ ```
103
+ // @fb-only
104
+
105
+ * `usePaginationFragment` behaves the same way as a `useFragment` (see the [Fragments](../../rendering/fragments/) section), so our list of friends is available under `data.friends.edges.node`, as declared by the fragment. However, it also has a few additions:
106
+ * It expects a fragment that is a connection field annotated with the `@connection` directive
107
+ * It expects a fragment that is annotated with the `@refetchable` directive. Note that `@refetchable` directive can only be added to fragments that are "refetchable", that is, on fragments that are on `Viewer`, on `Query`, on any type that implements `Node` (i.e. a type that has an `id` field), or on a `@fetchable` type. <FbInternalOnly> For more info on `@fetchable` types, see [this post](https://fb.workplace.com/groups/graphql.fyi/permalink/1539541276187011/). </FbInternalOnly>
108
+ * It takes two Flow type parameters: the type of the generated query (in our case `FriendsListPaginationQuery`), and a second type which can always be inferred, so you only need to pass underscore (`_`).
109
+
110
+ </OssOnly>
111
+
112
+ <DocsRating />
@@ -0,0 +1,87 @@
1
+ ---
2
+ id: streaming-pagination
3
+ title: Streaming Pagination
4
+ slug: /guided-tour/list-data/streaming-pagination/
5
+ description: Relay guide to streaming pagination
6
+ keywords:
7
+ - pagination
8
+ - usePaginationFragment
9
+ - connection
10
+ - streaming
11
+ ---
12
+
13
+ import DocsRating from '@site/src/core/DocsRating';
14
+ import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
15
+
16
+ <FbInternalOnly>
17
+
18
+ Additionally, we can combine `usePaginationFragment` with Relay's [Incremental Data Delivery](../../../guides/incremental-data-delivery/) capabilities in order to fetch a connection and incrementally receive each item in the connection as it becomes ready, instead of waiting for the whole list of items to be returned in a single payload. This can be useful when for example computing each item in the connection is an expensive operation in the server, and we want to be able to show the first item(s) in the list as soon as possible without blocking on *all* the items that we need to become available; for example, on News Feed a user could ideally see and start interacting with the first story while additional stories loaded in below.
19
+
20
+ </FbInternalOnly>
21
+
22
+ <OssOnly>
23
+
24
+ Additionally, we can combine `usePaginationFragment` with Relay's Incremental Data Delivery capabilities in order to fetch a connection and incrementally receive each item in the connection as it becomes ready, instead of waiting for the whole list of items to be returned in a single payload. This can be useful when for example computing each item in the connection is an expensive operation in the server, and we want to be able to show the first item(s) in the list as soon as possible without blocking on *all* the items that we need to become available; for example, on News Feed a user could ideally see and start interacting with the first story while additional stories loaded in below.
25
+
26
+ </OssOnly>
27
+
28
+ In order to do so, we can use the `@stream_connection` directive instead of the `@connection` directive:
29
+
30
+ ```js
31
+ import type {FriendsListComponent_user$key} from 'FriendsList_user.graphql';
32
+
33
+ const React = require('React');
34
+
35
+ const {graphql, usePaginationFragment} = require('react-relay');
36
+
37
+ type Props = {
38
+ user: FriendsListComponent_user$key,
39
+ };
40
+
41
+ function FriendsListComponent(props: Props) {
42
+ // ...
43
+
44
+ const {
45
+ data,
46
+ loadNext,
47
+ hasNext,
48
+ } = usePaginationFragment(
49
+ graphql`
50
+ fragment FriendsListComponent_user on User
51
+ @refetchable(queryName: "FriendsListPaginationQuery") {
52
+ name
53
+ friends(first: $count, after: $cursor)
54
+ @stream_connection(key: "FriendsList_user_friends", initial_count: 2,) {
55
+ edges {
56
+ node {
57
+ name
58
+ age
59
+ }
60
+ }
61
+ }
62
+ }
63
+ `,
64
+ props.user,
65
+ );
66
+
67
+ return (...);
68
+ }
69
+
70
+ module.exports = FriendsListComponent;
71
+ ```
72
+
73
+ Let's distill what's happening here:
74
+
75
+ * The `@stream_connection` directive can be used directly in place of the `@connection` directive; it accepts the same arguments as @connection plus additional, *optional* parameters to control streaming:
76
+ * `initial_count: Int`: A number (defaulting to zero) that controls how many items will be included in the initial payload. Any subsequent items are streamed, so when set to zero the list will initially be empty and all items will be streamed. Note that this number does not affect how many items are returned *total*, only how many items are included in the initial payload. For example, consider a product that today makes an initial fetch for 2 items and then *immediately* issues a pagination query to fetch 3 more. With streaming, this product could instead choose to fetch 5 items in the initial query with initial_count=2, in order to fetch the 2 items quickly while avoiding a round trip for the subsequent 3 items.
77
+ * As with regular usage of `usePaginationFragment`, the connection will be automatically updated as new items are streamed in from the server, and the component will re-render each time with the latest items in the connection.
78
+
79
+
80
+ <FbInternalOnly>
81
+
82
+ For more information, see our docs on [Incremental Data Delivery](../../../guides/incremental-data-delivery/#stream_connection).
83
+
84
+ </FbInternalOnly>
85
+
86
+
87
+ <DocsRating />
@@ -0,0 +1,51 @@
1
+ ---
2
+ id: retaining-queries
3
+ title: Retaining Queries
4
+ slug: /guided-tour/accessing-data-without-react/retaining-queries/
5
+ description: Relay guide to retaining queries
6
+ keywords:
7
+ - retaining
8
+ - query
9
+ - environment
10
+ - garbage collection
11
+ - gc
12
+ ---
13
+
14
+ import DocsRating from '@site/src/core/DocsRating';
15
+ import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
16
+
17
+ In order to manually retain a query so that the data it references isn’t garbage collected by Relay, we can use the `environment.retain` method:
18
+
19
+ ```js
20
+ const {
21
+ createOperationDescriptor,
22
+ getRequest,
23
+ graphql,
24
+ } = require('relay-runtime')
25
+
26
+ // Query graphql object
27
+ const query = graphql`...`;
28
+
29
+ // Construct Relay's internal representation of the query
30
+ const queryRequest = getRequest(query);
31
+ const queryDescriptor = createOperationDescriptor(
32
+ queryRequest,
33
+ variables
34
+ );
35
+
36
+ // Retain query; this will prevent the data for this query and
37
+ // variables from being garbage collected by Relay
38
+ const disposable = environment.retain(queryDescriptor);
39
+
40
+ // Disposing of the disposable will release the data for this query
41
+ // and variables, meaning that it can be deleted at any moment
42
+ // by Relay's garbage collection if it hasn't been retained elsewhere
43
+ disposable.dispose();
44
+ ```
45
+
46
+ :::note
47
+ Relay automatically manages the query data retention based on any mounted query components that are rendering the data, so you usually should not need to call retain directly within product code. For any advanced or special use cases, query data retention should usually be handled within infra-level code, such as a Router.
48
+ :::
49
+
50
+
51
+ <DocsRating />