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,664 @@
1
+ ---
2
+ id: server-3d
3
+ title: Server 3D
4
+ slug: /guides/data-driven-dependencies/server-3d/
5
+ description: Server side data driven dependencies (3D)
6
+ keywords:
7
+ - 3D
8
+ - Server 3D
9
+ - data driven dependencies
10
+ - module
11
+ - match
12
+ - MatchContainer
13
+ ---
14
+
15
+ import DocsRating from '@site/src/core/DocsRating';
16
+ import {FbInternalOnly, OssOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
17
+ // @fb-only
18
+
19
+ <FbInternalOnly>
20
+
21
+ :::note
22
+ Throughout this guide, we use `MatchContainer`. If you are in www, but not in Comet, you should use RelayFBMatchContainer.
23
+ :::
24
+
25
+ </FbInternalOnly>
26
+
27
+ <OssOnly>
28
+
29
+ :::warning
30
+ Server 3D requires configuring your server to support various features! It is unlikely to work in OSS without significant work. Relay does not claim to fully support Server 3D in OSS (yet), but [Client 3D](../client-3d/) is fully supported.
31
+ :::
32
+
33
+ </OssOnly>
34
+
35
+ Use server 3D when all the data fields used to render your 3D components are fetched from GraphQL servers.
36
+
37
+ ## Simple 3D with @module
38
+
39
+ The basic cases for 3D are the first two cases described in [Use Cases](../introduction/#use-cases): content that is typically missing (where the corresponding rendering code is typically unused) or a union of many types (where only some of the possible rendering code is typically used). These cases are supported with the `@module(name: String)` directive on fragment spreads, which specifies a React component to download only if the data exists and fragment's type matches.
40
+
41
+ ### @module Usage Guide
42
+
43
+ Let's walk through how to handle a comment that may contain an image attachment, where we only want to download the image rendering code when an image is actually present.
44
+
45
+ #### Server Changes
46
+
47
+ <FbInternalOnly>
48
+
49
+ * For each concrete (GraphQLObject) type that you want to use `@module` with, update the schema to use the `HasJSDependency` trait. In this case we'd add the trait to the type of the `Comment.image` field — let's say that is `CommentImage`:
50
+
51
+ ```php
52
+ <<
53
+ GraphQLObject('CommentImage', 'An image attached to a comment'),
54
+ Oncalls('<todo>')
55
+ >>
56
+ final class CommentImage ... {
57
+ // Note: Each type used with @module must use the `HasJSDependency` trait
58
+ // to allow dynamically loading a client-specified React component
59
+ use HasJSDependency;
60
+
61
+ ...
62
+ }
63
+ ```
64
+ * Rebuild the GraphQL schema with `phps graphql`.
65
+
66
+ </FbInternalOnly>
67
+
68
+ <OssOnly>
69
+
70
+ * For each concrete (GraphQLObject) type that you want to use `@module` with, update the schema to support the fields (`__fragment` and `__component`) that are present later in this document.
71
+
72
+ </OssOnly>
73
+
74
+ #### Client Changes
75
+
76
+ Your Relay fragment can now use `@module`. In this example, if the `comment.image` field is present (non-null), we load the `CommentImage.react` component and use the `CommentImage_image` fragment to load its data.
77
+
78
+ On the Relay side you'd write:
79
+
80
+ ```graphql
81
+ fragment Comment_comment on Comment {
82
+ image {
83
+ ...CommentImage_image
84
+ @module(name: "CommentImage.react")
85
+ }
86
+ }
87
+ ```
88
+
89
+ <FbInternalOnly>
90
+ Which the server receives as the following:
91
+
92
+ ```graphql
93
+ fragment Comment_comment on Comment {
94
+ image {
95
+ ... on CommentImage {
96
+ ...CommentImage_image
97
+ __component: js("CommentImage.react")
98
+ __fragment: js("CommentImage_image$normalization.graphql")
99
+ }
100
+ }
101
+ }
102
+ ```
103
+ </FbInternalOnly>
104
+
105
+ To consume the `comment.image` field and render the component when the data exists, you shouldn't statically require the component (which would introduce a static dependency) and instead use `MatchContainer` to return the dynamically selected component:
106
+
107
+ <FbInternalOnly>
108
+ Within Meta, use `CometRelay.MatchContainer` (Comet) or `RelayFBMatchContainer` (www).
109
+ </FbInternalOnly>
110
+
111
+ ```js
112
+ const {useFragment, graphql, MatchContainer} = require('react-relay');
113
+
114
+ function CommentRenderer(props) {
115
+ const comment = useFragment(
116
+ /* fragment Comment_comment from above */,
117
+ props.comment,
118
+ );
119
+
120
+ if (comment.image == null) {
121
+ // Handle cases where the field failed to load or was null
122
+ return null;
123
+ }
124
+ // MatchContainer may suspend while loading the component or its data,
125
+ // consider wrapping with React.Suspense.
126
+ return (
127
+ <Suspense fallback={null}>
128
+ <MatchContainer
129
+ // data for field containing @module selection
130
+ match={comment.image}
131
+ props={{ /* ...other props... */ }}
132
+ />
133
+ </Suspense>
134
+ );
135
+ }
136
+ module.exports = CommentRenderer;
137
+ ```
138
+
139
+ :::caution
140
+ When using MatchContainer, the component loaded using 3D needs to have the same prop name as the fragment suffix e.g. if your fragment is `Comment_comment`, your prop needs to be called `comment` instead of something like `comment$key`
141
+ :::
142
+
143
+ ## Advanced 3D with @match
144
+
145
+ In some cases a given piece of content can be rendered in a variety of different rendering strategies. In this case, the client and server have to negotiate to choose the ideal strategy for each piece of content: the content may be eligible to be rendered as SuperFancyMarkdown, but if the client doesn't support that type the app should fallback to just regular Markdown rather than showing nothing at all. Relay supports this client/server negotiation with the `@match` directive.
146
+
147
+ ### @match Design Principles
148
+
149
+ * The client specifies which strategies it supports (a given client may not support all possible strategies), how it will render that data (one React component per strategy), and what data it needs (a GraphQL fragment for each strategy, describing the React component's data dependencies).
150
+ * The server - specifically *product logic in the schema* - selects the rendering strategy to use, selecting the "best" strategy given the user, data, and the client's supported strategies.
151
+ * The code (Component) and data (GraphQL) for the selected strategy is downloaded *dynamically* once the strategy is selected. Data is downloaded as normal GraphQL data, and metadata about the code is sent down in a side-channel (technically, in the [`extensions`](https://graphql.org/learn/response/#extensions) field of the GraphQL payload).
152
+
153
+ ### @match Usage Guide
154
+
155
+ Let's walk through the steps to implementing an example of adding a new data-driven dependency for a `Comment` type with `markdown` and `plaintext` rendering strategies.
156
+
157
+ #### Server Changes
158
+
159
+ <FbInternalOnly>
160
+
161
+ * Define a new `GraphQLUnion` type with a variant for each rendering strategy:
162
+
163
+ ```php
164
+ <<
165
+ GraphQLUnion('CommentRenderer', 'Data-driven dependency for comments...'),
166
+ Oncalls('<todo>')
167
+ >>
168
+ interface ICommentRenderer extends IGraphQLUnion {}
169
+
170
+ <<
171
+ GraphQLObject('CommentMarkdownRenderer', 'Comment with markdown rendering'),
172
+ Oncalls('<todo>')
173
+ >>
174
+ final class CommentMarkdownRenderer implements ICommentRenderer {
175
+ // Note: Each class in the union must use the `HasJSDependency` trait to allow
176
+ // dynamically loading a client-specified React compon
177
+ use HasJSDependency;
178
+
179
+ <<GraphQLField('markdown', 'Markdown text')>>
180
+ public function markdown(): string {
181
+ return 'markdown'; // todo: load markdown content from the comment
182
+ }
183
+ }
184
+
185
+ <<
186
+ GraphQLObject('CommentPlaintextRenderer', 'Comment with plaintext rendering'),
187
+ Oncalls('<todo>')
188
+ >>
189
+ final class CommentPlaintextRenderer implements ICommentRenderer {
190
+ // Note: Each class in the union must use the `HasJSDependency` trait to allow
191
+ // dynamically loading a client-specified React compon
192
+ use HasJSDependency;
193
+
194
+ <<GraphQLField('plaintext', 'Plaintext')>>
195
+ public function plaintext(): string {
196
+ return 'plaintext'; // todo: load text content from the comment
197
+ }
198
+ }
199
+ ```
200
+ * Add a new field on the `Comment` type that accepts a `Traversable<string> supported` argument listing the client's supported strategies, and returns one of the union values to indicate the selected strategy:
201
+
202
+ ```php
203
+ <<
204
+ GraphQLObject('Comment', 'Comment on a post'),
205
+ Oncalls('<todo>')
206
+ >>
207
+ final class Comment {
208
+ <<GraphQLField(
209
+ 'comment_content_renderer',
210
+ 'Field that returns a rendering strategy for the main content of the comment',
211
+ )>>
212
+ public function commentContentRenderer(Traversable<string> $supported): ICommentRenderer {
213
+ if (C\contains($supported, 'CommentMarkdownRenderer') && userIsEligibleForMarkdownContent()) {
214
+ return new CommentMarkdownRenderer();
215
+ }
216
+ return new CommentPlaintextRenderer();
217
+ }
218
+ }
219
+ ```
220
+ * Rebuild the GraphQL schema with `arc rebuild`.
221
+
222
+ #### Choose Render Strategy API
223
+
224
+ In some cases, your use case might require multiple and more complex checks in order to choose a proper render strategy. For those cases, you can extend your strategies with `IRenderStrategy`. So, the example above could be extended aggregating `CommentMarkupRenderer` simply writing the following code:
225
+
226
+ ```php
227
+ <<
228
+ GraphQLObject('CommentMarkupRenderer', 'Comment with markup rendering'),
229
+ Oncalls('<todo>')
230
+ >>
231
+ final class CommentMarkupRenderer implements ICommentRenderer, IRenderStrategy {
232
+
233
+ use HasJSDependency;
234
+
235
+ <<GraphQLField('markup', 'Markdown text')>>
236
+ public function markdown(): string {
237
+ return 'markup'; // todo: load markup content from the comment
238
+ }
239
+
240
+ <<__Override>>
241
+ public async function genShouldShow(): Awaitable<bool> {
242
+ return userIsEligibleForMarkupContent();
243
+ }
244
+ }
245
+ final class CommentMarkdownRenderer implements ICommentRenderer, IRenderStrategy {
246
+ // ...
247
+ <<__Override>>
248
+ public async function genShouldShow(): Awaitable<bool> {
249
+ return userIsEligibleForMarkdownContent();
250
+ }
251
+ }
252
+
253
+ final class CommentPlaintextRenderer implements ICommentRenderer, IRenderStrategy {
254
+ // ...
255
+ <<__Override>>
256
+ public async function genShouldShow(): Awaitable<bool> {
257
+ // By default we want to use this strategy as a fallback
258
+ return true;
259
+ }
260
+ }
261
+ ```
262
+ having all RendererStrategies implementing IRenderStrategy, we can choose the strategy using `RenderStrategySelector::genChooseStrategy`. Making sure to place strategies in the proper order, since the first strategy that `genShouldShow` returns true will be picked as:
263
+
264
+ ```php
265
+ public function commentContentRenderer(Traversable<string> $supported): Awaitable<ICommentRenderer> {
266
+ return await RenderStrategySelector::genChooseStrategy(
267
+ vec[
268
+ CommentMarkdownRenderer(),
269
+ CommentMarkupRenderer(),
270
+ CommentPlaintextRenderer(),
271
+ ],
272
+ $supported,
273
+ )
274
+ }
275
+ ```
276
+
277
+ </FbInternalOnly>
278
+
279
+ <OssOnly>
280
+
281
+ * Define a new `GraphQLUnion` type with a variant for each rendering strategy.
282
+ * Add a new field on the `Comment` type that accepts an `Array<string> supported` argument listing the client's supported strategies, and returns one of the union values to indicate the selected strategy.
283
+
284
+ For example:
285
+ ```graphql
286
+ type CommentMarkdownRenderer {
287
+ markdown_content: String
288
+ __component: JSDependency
289
+ __fragment: JSDependency
290
+ }
291
+
292
+ type CommentPlaintextRenderer {
293
+ plaintext_content: String
294
+ __component: JSDependency
295
+ __fragment: JSDependency
296
+ }
297
+
298
+ union CommentRenderer = CommentMarkdownRenderer | CommentPlaintextRenderer
299
+
300
+ type Comment {
301
+ # ... other fields here
302
+ comment_content_renderer(supported: [String!]): CommentRenderer
303
+ }
304
+ ```
305
+
306
+ </OssOnly>
307
+
308
+
309
+ #### Client Changes
310
+
311
+ Your Relay fragment can now use `@match` to specify that for the `comment_content_renderer` field, we expect dependencies to be decided by the data. In this example, if the `comment_content_renderer` field is of type `CommentMarkdownRenderer`, we load the `CommentMarkdownRenderer.react` component and use the `CommentMarkdownRenderer_comment` fragment to load its data. Similar for the plaintext variant.
312
+
313
+ :::caution
314
+ The inline fragments annotated with `@module` on the same parent 3D fragment must be on distinct concrete types. If they are on the same concrete type, the relay compiler will report an error. So in the example below, `CommentMarkdownRenderer_comment` must be on a different concrete type than `CommentPlaintextRenderer_comment` (for example, the former could be on a `MarkdownComment` type, and the latter on a `PlaintextComment` type. Both could implement a parent interface `Comment`).
315
+ :::
316
+
317
+ On the Relay side you'd write:
318
+
319
+ ```graphql
320
+ fragment Comment_comment on Comment {
321
+ comment_content_renderer @match {
322
+ ...CommentMarkdownRenderer_comment
323
+ @module(name: "CommentMarkdownRenderer.react")
324
+
325
+ ...CommentPlaintextRenderer_comment
326
+ @module(name: "CommentPlaintextRenderer.react")
327
+ }
328
+ }
329
+ ```
330
+ Which the server receives as the following - note that the `supported` argument is generated automatically based on the types that we have provided fragments for above:
331
+
332
+ <FbInternalOnly>
333
+
334
+ ```graphql
335
+ fragment Comment_comment on Comment {
336
+ comment_content_renderer(supported: ["CommentMarkdownRenderer", "CommentPlaintextRenderer"]) {
337
+ ... on CommentMarkdownRenderer {
338
+ ...CommentMarkdownRenderer_comment
339
+ __component: js("CommentMarkdownRenderer.react")
340
+ __fragment: js("CommentMarkdownRenderer_comment$normalization.graphql")
341
+ }
342
+ ... on CommentPlaintextRenderer {
343
+ ...CommentPlaintextRenderer_comment
344
+ __component: js("CommentPlaintextRenderer.react")
345
+ __fragment: js("CommentPlaintextRenderer_comment$normalization.graphql")
346
+ }
347
+ }
348
+ }
349
+ ```
350
+
351
+ </FbInternalOnly>
352
+
353
+ <OssOnly>
354
+
355
+ ```graphql
356
+ fragment Comment_comment on Comment {
357
+ comment_content_renderer(supported: ["CommentMarkdownRenderer", "CommentPlaintextRenderer"]) {
358
+ ... on CommentMarkdownRenderer {
359
+ ...CommentMarkdownRenderer_comment
360
+ }
361
+ ... on CommentPlaintextRenderer {
362
+ ...CommentPlaintextRenderer_comment
363
+ }
364
+ }
365
+ }
366
+ ```
367
+
368
+ </OssOnly>
369
+
370
+ To consume the comment_content_renderer field and render the appropriate container, you shouldn't statically require the component (which would introduce a static dependency) and instead use `MatchContainer` to return the dynamically selected component:
371
+
372
+ ```js
373
+ const React = require('React');
374
+ const {Suspense} = React;
375
+ const {graphql, useFragment, MatchContainer} = require('react-relay');
376
+
377
+ function CommentRenderer(props) {
378
+ const comment = useFragment(
379
+ /* fragment from above */,
380
+ props.comment,
381
+ );
382
+
383
+ if (comment.comment_content_renderer == null) {
384
+ // Handle cases where the field failed to load or was null
385
+ return null;
386
+ }
387
+
388
+ // MatchContainer may suspend while loading the component/its data,
389
+ // consider wrapping with React.Suspense.
390
+ return (
391
+ <Suspense fallback={null}>
392
+ <MatchContainer
393
+ // data for field containing at-module selection
394
+ match={comment.comment_content_renderer}
395
+ props={{/* other props */}}
396
+ />
397
+ </Suspense>
398
+ );
399
+ }
400
+ module.exports = CommentRenderer;
401
+ ```
402
+
403
+ :::caution
404
+ When using MatchContainer, the component loaded using 3D needs to have the same prop name as the fragment suffix e.g. if your fragment is `Comment_comment`, your prop needs to be called `comment` instead of something like `comment$key`
405
+ :::
406
+
407
+ ## Multiple 3D Selections Per Fragment
408
+
409
+ If your component needs to select multiple data-driven dependencies in a single fragment, each field must be named with a distinct `key`. The key can be provided by adding the `@match` directive on the parent field:
410
+
411
+ ```graphql
412
+ # DOESN'T WORK
413
+ fragment Example_comment on Comment {
414
+ comment_content_renderer @match {
415
+ ...CommentMarkdownRenderer_comment
416
+ @module(name: "CommentMarkdownRenderer.react")
417
+ }
418
+ attachments {
419
+ attachment_renderer {
420
+ ...CommentAttachmentPhotoRenderer_comment
421
+ @module(name: "CommentPhotoRenderer.react")
422
+ }
423
+ }
424
+ }
425
+ ```
426
+
427
+ This will fail with a message such as:
428
+
429
+ ```
430
+ `Error: Invalid @module selection: documents with multiple fields containing 3D
431
+ selections must specify a unique 'key' value for each field:
432
+ use 'attachment_renderer @match(key: "ExampleComment_<localName>")'.`
433
+ ```
434
+
435
+ In this case, follow the suggestion in the error and add `@match(key: "...")` on the second 3D field (`attachment_renderer` in this case):
436
+
437
+ ```
438
+ // OK - different keys with @match
439
+ fragment Example_comment on Comment {
440
+ comment_content_renderer @match {
441
+ ...CommentMarkdownRenderer_comment
442
+ @module(name: "CommentMarkdownRenderer.react")
443
+ }
444
+ attachments {
445
+ attachment_renderer @match(key: "Example_comment__attachment") {
446
+ ...CommentAttachmentPhotoRenderer_comment
447
+ @module(name: "CommentPhotoRenderer.react")
448
+ }
449
+ }
450
+ }
451
+ ```
452
+
453
+ Internally, Relay uses the 'key' value to isolate the results of each field in the store. This ensures that even if both fields return the same object, that the results can't collide.
454
+
455
+ ## Usage with Relay Hooks
456
+
457
+ The preferred way of using 3D is with the [`useFragment`](../../../api-reference/use-fragment/) API.
458
+
459
+ ```js
460
+ // CommentRenderer.react.js
461
+
462
+ const {graphql, useFragment, MatchContainer} = require('react-relay');
463
+
464
+ function CommentRenderer(props) {
465
+ const comment = useFragment(
466
+ graphql`
467
+ fragment Comment_comment on Comment {
468
+ image {
469
+ ...CommentImageRenderer_image @module(name: "CommentImageRenderer.react")
470
+ }
471
+ }
472
+ `,
473
+ props.comment,
474
+ );
475
+
476
+ if (comment.image == null) {
477
+ // Handle cases where the field failed to load or was null
478
+ return null;
479
+ }
480
+
481
+ // MatchContainer may suspend while loading the component/its data,
482
+ // consider wrapping with React.Suspense.
483
+ return (
484
+ <Suspense fallback={null}>
485
+ <MatchContainer
486
+ // data for field containing @module selection
487
+ match={comment.image}
488
+ props={{...other props...}}
489
+ />
490
+ </Suspense>
491
+ );
492
+ }
493
+ module.exports = CommentRenderer;
494
+ ```
495
+
496
+ // @fb-only
497
+
498
+ The component that is dynamically loaded via 3D can also be a component that uses `useFragment`:
499
+
500
+ ```js
501
+ // CommentImageRenderer.react.js
502
+ import type {CommentImageRenderer_image$key} from 'CommentImageRenderer_image.graphql'
503
+
504
+ const {useFragment} = require('react-relay');
505
+
506
+ type Props = {
507
+ image: CommentImageRenderer_image$key,
508
+ };
509
+
510
+ function CommentImageRenderer(props) {
511
+ const data = useFragment(
512
+ graphql`
513
+ fragment CommentImageRenderer_image on Image {
514
+ src
515
+ }
516
+ `,
517
+ props.image,
518
+ );
519
+
520
+ return (...);
521
+ }
522
+
523
+ module.exports = CommentImageRenderer;
524
+ ```
525
+
526
+ <FbInternalOnly>
527
+
528
+ ## Using non-React modules
529
+
530
+ The typical usage of data-driven dependencies is to dynamically load modules that export a React component with data-dependencies expressed via Relay. However, Relay also supports dynamically loading *arbitrary* JS modules. This works the same `@match` / `@module` syntax, but (as you may expect) `MatchContainer` won't work for this case. Instead, use `ModuleResource.read()`. The above example using `MatchContainer` can be rewritten to manually read and use the `@module` result:
531
+
532
+ :::tip
533
+ In www, outside of Comet, you should use `RelayFBModuleResource.read()` instead of `ModuleResource.read()`.
534
+ :::
535
+
536
+ ```js
537
+ const React = require('React');
538
+ const {Suspense} = React;
539
+ const {graphql, useFragment, ModuleResource} = require('react-relay');
540
+ const CommentFragment = graphql`
541
+ fragment Comment_comment on Comment {
542
+ comment_content_renderer @match {
543
+ ...CommentMarkdownRenderer_comment
544
+ @module(name: "CommentMarkdownRenderer.react")
545
+ ...CommentPlaintextRenderer_comment
546
+ @module(name: "CommentPlaintextRenderer.react")
547
+ }
548
+ }
549
+ `;
550
+ function CommentRenderer(props) {
551
+ const comment = useFragment(
552
+ CommentFragment,
553
+ props.comment,
554
+ );
555
+ if (comment.image == null) {
556
+ // Handle cases where the field failed to load or was null
557
+ return null;
558
+ }
559
+ // NOTE: this will suspend if the module is not loaded:
560
+ // the *parent* component should wrap this one in a Suspense boundary
561
+ // MatchedModule will be:
562
+ // - null if there was no match
563
+ // - CommentMarkdownRenderer.react if the result was markdown
564
+ // - CommentPlaintextRenderer.react if the result was plaintext
565
+
566
+ const MatchedModule = ModuleResource.read(comment.image);
567
+
568
+ if (MatchedModule == null) {
569
+ return null; // no match
570
+ }
571
+ // Here we ensure that all possible matched components accept the data
572
+ // on the same prop key, in this case 'comment'
573
+ // Note that MatchContainer automatically determines the
574
+ // correct prop key to use for the matched data.
575
+ return (
576
+ <MatchedModule
577
+ comment={comment.image}
578
+ />
579
+ );
580
+ }
581
+ module.exports = CommentRenderer;
582
+ ```
583
+
584
+ You can also use `@module` directly to load a non-React module for a field if it isn't null (without using `@match`), and similarly consume the module using `ModuleResource.read()`:
585
+
586
+ :::tip
587
+ In www, outside of Comet, you should use `RelayFBModuleResource.read()` instead of `ModuleResource.read()`.
588
+ :::
589
+
590
+ ```js
591
+ function CommentRenderer(props) {
592
+ const comment = useFragment(
593
+ graphql`
594
+ fragment Comment_comment on Comment {
595
+ image {
596
+ ...CommentImage_image
597
+ @module(name: "ImageProcessingModule")
598
+ }
599
+ }
600
+ `,
601
+ props.comment,
602
+ );
603
+
604
+ if (comment.image == null) {
605
+ // Handle cases where the field failed to load or was null
606
+ return null;
607
+ }
608
+
609
+ // NOTE: this will suspend if the module is not loaded
610
+ const ImageProcessingModule = ModuleResource.read(comment.image);
611
+
612
+ if (ImageProcessingModule == null) {
613
+ return null; // no match
614
+ }
615
+
616
+ // ...
617
+ }
618
+ ```
619
+
620
+ **Note:** `@module` requires a fragment, which cannot be empty. If you don't want to fetch any data from the server (only conditionally files), you can define a "dummy" fragment for your field:
621
+
622
+ ```javascript
623
+ // Define a fragment as a wrapper to use with @module
624
+ // The fragment below will be able to reference this fragment by name
625
+ graphql`
626
+ fragment FragmentForModule_image on Image {
627
+ __typename # only use __typename here since we don't need any data
628
+ }
629
+ `;
630
+
631
+ function CommentRenderer(props) {
632
+ const comment = useFragment(
633
+ graphql`
634
+ fragment Comment_comment on Comment {
635
+ image {
636
+ # Spread wrapper fragment
637
+ ...FragmentForModule_image
638
+ @module(name: "ImageProcessingModule")
639
+ }
640
+ }
641
+ `,
642
+ props.comment,
643
+ );
644
+
645
+ // ...
646
+ }
647
+ ```
648
+
649
+ </FbInternalOnly>
650
+
651
+ ## Important Notes / Troubleshooting
652
+
653
+
654
+ * Note that `MatchContainer` will suspend until the selected component finishes loading, so be sure to wrap it in a `Suspense` placeholder.
655
+
656
+ <FbInternalOnly>
657
+
658
+ ## ServerCallableModule Is No Longer Required
659
+
660
+ Usage of 3D **previously** required adding an `@ServerCallableModule` annotation to components loaded with `@module`. **This annotation is no longer required**. You may see diffs titled "[Codemod][DeadServerCallable]" that remove these now-unnecessary annotations, these diffs are expected and safe to land so long as they are only removing these annotations and not accidentally making other changes (i.e. please sanity-check the bot!).
661
+
662
+ </FbInternalOnly>
663
+
664
+ <DocsRating />