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,511 @@
1
+ ---
2
+ id: imperatively-modifying-linked-fields
3
+ title: Imperatively modifying linked fields
4
+ slug: /guided-tour/updating-data/imperatively-modifying-linked-fields/
5
+ description: Using readUpdatableQuery to update linked fields in the store
6
+ keywords:
7
+ - record source
8
+ - store
9
+ - updater
10
+ - typesafe updaters
11
+ - readUpdatableQuery
12
+ - readUpdatableFragment
13
+ - updatable
14
+ - assignable
15
+ ---
16
+
17
+ import DocsRating from '@site/src/core/DocsRating';
18
+ import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
19
+
20
+ :::note
21
+ See also [using readUpdatableQuery to update scalar fields in the store](../imperatively-modifying-store-data).
22
+ :::
23
+
24
+
25
+ The examples in the [previous section](../imperatively-modifying-store-data/) showed how to use the `readUpdatableQuery` API to update scalar fields like `is_new_comment` and `is_selected`.
26
+
27
+ The examples did **not** cover how to assign to linked fields. Let's start with an example of a component which allows the user of the application to update the Viewer's `best_friend` field.
28
+
29
+ ## Example: setting the viewer's best friend
30
+
31
+ In order to assign a viewer's best friend, that viewer must have such a field. It may be defined by the server schema, or it may be defined locally in a schema extension as follows:
32
+
33
+ ```graphql
34
+ extend type Viewer {
35
+ best_friend: User,
36
+ }
37
+ ```
38
+
39
+ Next, let's define a fragment and give it the `@assignable` directive, making it an **assignable fragment**. Assignable fragments can only contain a single field, `__typename`. This fragment will be on the `User` type, which is the type of the `best_friend` field.
40
+
41
+ ```js
42
+ // AssignBestFriendButton.react.js
43
+ graphql`
44
+ fragment AssignBestFriendButton_assignable_user on User @assignable {
45
+ __typename
46
+ }
47
+ `;
48
+ ```
49
+
50
+ The fragment must be spread at both the source (i.e. on the viewer's new best friend), and at the destination (within the viewer's `best_friend` field in the updatable query).
51
+
52
+ Lets define a component with a fragment where we spread `AssignBestFriendButton_assignable_user`. This user will be the viewer's new best friend.
53
+
54
+ ```js
55
+ // AssignBestFriendButton.react.js
56
+ import type {AssignBestFriendButton_user$key} from 'AssignBestFriendButton_user.graphql';
57
+
58
+ const {useFragment} = require('react-relay');
59
+
60
+ export default function AssignBestFriendButton({
61
+ someTypeRef: AssignBestFriendButton_user$key,
62
+ }) {
63
+ const data = useFragment(graphql`
64
+ fragment AssignBestFriendButton_someType on SomeType {
65
+ user {
66
+ name
67
+ ...AssignBestFriendButton_assignable_user
68
+ }
69
+ }
70
+ `, someTypeRef);
71
+
72
+ // We will replace this stub with the real thing below.
73
+ const onClick = () => {};
74
+
75
+ return (<button onClick={onClick}>
76
+ Declare {data.user?.name ?? 'someone with no name'} your new best friend!
77
+ </button>);
78
+ }
79
+ ```
80
+
81
+ That's great! Now, we have a component that renders a button. Let's fill out that button's click handler by using the `commitLocalUpdate` and `readUpdatableQuery` APIs to assign `viewer.best_friend`.
82
+
83
+ * In order to make it valid to assign `data.user` to `best_friend`, we must **also** spread `AssignBestFriendButton_assignable_user` under the `best_friend` field in the viewer in the updatable query or fragment.
84
+
85
+ ```js
86
+ import type {RecordSourceSelectorProxy} from 'react-relay';
87
+
88
+ const {commitLocalUpdate, useRelayEnvironment} = require('react-relay');
89
+
90
+ // ...
91
+
92
+ const environment = useRelayEnvironment();
93
+ const onClick = () => {
94
+ const updatableData = commitLocalUpdate(
95
+ environment,
96
+ (store: RecordSourceSelectorProxy) => {
97
+ const {updatableData} = store.readUpdatableQuery(
98
+ graphql`
99
+ query AssignBestFriendButtonUpdatableQuery
100
+ @updatable {
101
+ viewer {
102
+ best_friend {
103
+ ...AssignBestFriendButton_assignable_user
104
+ }
105
+ }
106
+ }
107
+ `,
108
+ {}
109
+ );
110
+
111
+ if (data.user != null && updatableData.viewer != null) {
112
+ updatableData.viewer.best_friend = data.user;
113
+ }
114
+ }
115
+ );
116
+ };
117
+ ```
118
+
119
+ ### Putting it all together
120
+
121
+ The full example is as follows:
122
+
123
+ ```graphql
124
+ extend type Viewer {
125
+ best_friend: User,
126
+ }
127
+ ```
128
+
129
+ ```js
130
+ // AssignBestFriendButton.react.js
131
+ import type {AssignBestFriendButton_user$key} from 'AssignBestFriendButton_user.graphql';
132
+ import type {RecordSourceSelectorProxy} from 'react-relay';
133
+
134
+ const {commitLocalUpdate, useFragment, useRelayEnvironment} = require('react-relay');
135
+
136
+ graphql`
137
+ fragment AssignBestFriendButton_assignable_user on User @assignable {
138
+ __typename
139
+ }
140
+ `;
141
+
142
+ export default function AssignBestFriendButton({
143
+ someTypeRef: AssignBestFriendButton_someType$key,
144
+ }) {
145
+ const data = useFragment(graphql`
146
+ fragment AssignBestFriendButton_someType on SomeType {
147
+ user {
148
+ name
149
+ ...AssignBestFriendButton_assignable_user
150
+ }
151
+ }
152
+ `, someTypeRef);
153
+
154
+ const environment = useRelayEnvironment();
155
+ const onClick = () => {
156
+ const updatableData = commitLocalUpdate(
157
+ environment,
158
+ (store: RecordSourceSelectorProxy) => {
159
+ const {updatableData} = store.readUpdatableQuery(
160
+ graphql`
161
+ query AssignBestFriendButtonUpdatableQuery
162
+ @updatable {
163
+ viewer {
164
+ best_friend {
165
+ ...AssignBestFriendButton_assignable_user
166
+ }
167
+ }
168
+ }
169
+ `,
170
+ {}
171
+ );
172
+
173
+ if (data.user != null && updatableData.viewer != null) {
174
+ updatableData.viewer.best_friend = data.user;
175
+ }
176
+ }
177
+ );
178
+ };
179
+
180
+ return (<button onClick={onClick}>
181
+ Declare {user.name ?? 'someone with no name'} my best friend!
182
+ </button>);
183
+ }
184
+ ```
185
+
186
+ Let's recap what is happening here.
187
+
188
+ * We are writing a component in which clicking a button results in a user is being assigned to `viewer.best_friend`. After this button is clicked, all components which were previously reading the `viewer.best_friend` field will be re-rendered, if necessary.
189
+ * The source of the assignment is a user where an **assignable fragment** is spread.
190
+ * The target of the assignment is accessed using the `commitLocalUpdate` and `readUpdatableQuery` APIs.
191
+ * The query passed to `readUpdatableQuery` must include the `@updatable` directive.
192
+ * The target field must have that same **assignable fragment** spread.
193
+ * We are checking whether `data.user` is not null before assigning. This isn't strictly necessary. However, if we assign `updatableData.viewer.best_friend = null`, we will be nulling out the linked field in the store! This is (probably) not what you want.
194
+
195
+ ## Pitfalls
196
+
197
+ * Note that there are no guarantees about what fields are present on the assigned user. This means that anything that consumes an updated field has no guarantee that the required fields were fetched and are present on the assigned object.
198
+
199
+ <FbInternalOnly>
200
+
201
+ :::note
202
+
203
+ It is technically feasible to add fields to the assignable fragment, which would have the effect of guaranteeing that certain fields are present in the assigned object.
204
+
205
+ If this is a need, please reach out to [Relay Support](https://fb.workplace.com/groups/relay.support).
206
+
207
+ :::
208
+
209
+ </FbInternalOnly>
210
+
211
+ ## Example: Assigning to a list
212
+
213
+ Let's modify the previous example to append the user to a list of best friends. In this example, the following principle is relevant:
214
+
215
+ > Every assigned linked field (i.e. the right hand side of the assignment) **must originate in a read-only fragment, query, mutation or subscription**.
216
+
217
+ This means that `updatableData.foo = updatableData.foo` is invalid. For the same reason, `updatableData.viewer.best_friends = updatableData.viewer.best_friends.concat([newBestFriend])` is invalid. To work around this restriction, we must select the existing best friends from a read-only fragment, and perform the assignment as follows: `viewer.best_friends = existing_list.concat([newBestFriend])`.
218
+
219
+ Consider the following full example:
220
+
221
+ ```graphql
222
+ extend type Viewer {
223
+ # We are now defined a "best_friends" field instead of a "best_friend" field
224
+ best_friends: [User!],
225
+ }
226
+ ```
227
+
228
+ ```js
229
+ // AssignBestFriendButton.react.js
230
+ import type {AssignBestFriendButton_user$key} from 'AssignBestFriendButton_user.graphql';
231
+ import type {AssignBestFriendButton_viewer$key} from 'AssignBestFriendButton_viewer';
232
+
233
+ import type {RecordSourceSelectorProxy} from 'react-relay';
234
+
235
+ const {commitLocalUpdate, useFragment, useRelayEnvironment} = require('react-relay');
236
+
237
+ graphql`
238
+ fragment AssignBestFriendButton_assignable_user on User @assignable {
239
+ __typename
240
+ }
241
+ `;
242
+
243
+ export default function AssignBestFriendButton({
244
+ someTypeRef: AssignBestFriendButton_someType$key,
245
+ viewerFragmentRef: AssignBestFriendButton_viewer$key,
246
+ }) {
247
+ const data = useFragment(graphql`
248
+ fragment AssignBestFriendButton_someType on SomeType {
249
+ user {
250
+ name
251
+ ...AssignBestFriendButton_assignable_user
252
+ }
253
+ }
254
+ `, someTypeRef);
255
+
256
+ const viewer = useFragment(graphql`
257
+ fragment AssignBestFriendButton_viewer on Viewer {
258
+ best_friends {
259
+ # since viewer.best_friends appears in the right hand side of the assignment
260
+ # (i.e. updatableData.viewer.best_friends = viewer.best_friends.concat(...)),
261
+ # the best_friends field must contain the correct assignable fragment spread
262
+ ...AssignBestFriendButton_assignable_user
263
+ }
264
+ }
265
+ `, viewerRef);
266
+
267
+ const environment = useRelayEnvironment();
268
+ const onClick = () => {
269
+ commitLocalUpdate(
270
+ environment,
271
+ (store: RecordSourceSelectorProxy) => {
272
+ const {updatableData} = store.readUpdatableQuery(
273
+ graphql`
274
+ query AssignBestFriendButtonUpdatableQuery
275
+ @updatable {
276
+ viewer {
277
+ best_friends {
278
+ ...AssignBestFriendButton_assignable_user
279
+ }
280
+ }
281
+ }
282
+ `,
283
+ {}
284
+ );
285
+
286
+ if (data.user != null && updatableData.viewer != null && viewer.best_friends != null) {
287
+ updatableData.viewer.best_friends = [
288
+ ...viewer.best_friends,
289
+ data.user,
290
+ ];
291
+ }
292
+ }
293
+ );
294
+ };
295
+
296
+ return (<button onClick={onClick}>
297
+ Add {user.name ?? 'someone with no name'} to my list of best friends!
298
+ </button>);
299
+ }
300
+ ```
301
+
302
+ ## Example: assigning from an abstract field to a concrete field
303
+
304
+ If you are assigning from an abstract field, e.g. a `Node` to a `User` (which implements `Node`), you must use an inline fragment to refine the `Node` type to `User`. Consider this snippet:
305
+
306
+ ```js
307
+ const data = useFragment(graphql`
308
+ fragment AssignBestFriendButton_someType on Query {
309
+ node(id: "4") {
310
+ ... on User {
311
+ __typename
312
+ ...AssignBestFriendButton_assignable_user
313
+ }
314
+ }
315
+ }
316
+ `, queryRef);
317
+
318
+ const environment = useRelayEnvironment();
319
+ const onClick = () => {
320
+ const updatableData = commitLocalUpdate(
321
+ environment,
322
+ (store: RecordSourceSelectorProxy) => {
323
+ const {updatableData} = store.readUpdatableQuery(
324
+ graphql`
325
+ query AssignBestFriendButtonUpdatableQuery
326
+ @updatable {
327
+ viewer {
328
+ best_friend {
329
+ ...AssignBestFriendButton_assignable_user
330
+ }
331
+ }
332
+ }
333
+ `,
334
+ {}
335
+ );
336
+
337
+ if (data.node != null && data.node.__typename === "User" && updatableData.viewer != null) {
338
+ updatableData.viewer.best_friend = data.node;
339
+ }
340
+ }
341
+ );
342
+ };
343
+ ```
344
+
345
+ In this snippet, we do two things:
346
+
347
+ * We use an inline fragment to refine the `Node` type to the `User` type. Inside of this refinement, we spread the assignable fragment.
348
+ * We check that `data.node.__typename === "User"`. This indicates to Flow that within that if block, `data.node` is known to be a user, and therefore `updatableData.viewer.best_friend = data.node` can typecheck.
349
+
350
+ ## Example: assigning to an interface when the source is guaranteed to implement that interface
351
+
352
+ You may wish to assign to a destination field that has an interface type (in this example, `Actor`). If the source field is guaranteed to implement that interface, then assignment is straightforward.
353
+
354
+ For example, the source might have the same interface type or have a concrete type (`User`, in this example) that implements that interface.
355
+
356
+ Consider the following snippet:
357
+
358
+ ```js
359
+ graphql`
360
+ fragment Foo_actor on Actor @assignable {
361
+ __typename
362
+ }
363
+ `;
364
+
365
+ const data = useFragment(graphql`
366
+ fragment Foo_query on Query {
367
+ user {
368
+ ...Foo_actor
369
+ }
370
+ viewer {
371
+ actor {
372
+ ...Foo_actor
373
+ }
374
+ }
375
+ }
376
+ `, queryRef);
377
+
378
+ const environment = useRelayEnvironment();
379
+ const onClick = () => {
380
+ commitLocalUpdate(environment, store => {
381
+ const {updatableData} = store.readUpdatableQuery(
382
+ graphql`
383
+ query FooUpdatableQuery @updatable {
384
+ viewer {
385
+ actor {
386
+ ...Foo_actor
387
+ }
388
+ }
389
+ }
390
+ `,
391
+ {}
392
+ );
393
+
394
+ // Assigning the user works as you would expect
395
+ if (updatableData.viewer != null && data.user != null) {
396
+ updatableData.viewer = data.user;
397
+ }
398
+
399
+ // As does assigning the viewer
400
+ if (updatableData.viewer != null && data.viewer?.actor != null) {
401
+ updatableData.viewer = data.viewer.actor;
402
+ }
403
+ });
404
+ };
405
+ ```
406
+
407
+ ## Example: assigning to an interface when the source is **not** guaranteed to implement that interface
408
+
409
+ You may wish to assign to a destination field that has an interface type (in this example, `Actor`). If the source type (e.g. `Node`) is **not** known to implement that interface, then an extra step is involved: validation.
410
+
411
+ <FbInternalOnly>
412
+
413
+ :::note
414
+
415
+ With additional changes to Relay's type generation, this can be made simpler. Please reach out to [Robert Balicki](https://www.internalfb.com/profile/view/1238951) if this is a pain point for you.
416
+
417
+ :::
418
+
419
+ </FbInternalOnly>
420
+
421
+ In order to understand why, some background is necessary. The flow type for the setter for an interface field might look like:
422
+
423
+ ```js
424
+ set actor(value: ?{
425
+ +__id: string,
426
+ +__isFoo_actor: string,
427
+ +$fragmentSpreads: Foo_actor$fragmentType,
428
+ ...
429
+ }): void,
430
+ ```
431
+
432
+ The important thing to note is that the setter expects an object with a non-null `__isFoo_actor` field.
433
+
434
+ When an assignable fragment with an abstract type is spread in a regular fragment, it results in an `__isFoo_actor: string` selection that is not optional if the type is known to implement the interface, and optional otherwise.
435
+
436
+ Since a `Node` is **not** guaranteed to implement `Actor`, when the Relay compiler encounters the selection `node(id: "4") { ...Foo_actor }`, it will emit an optional field (`__isFoo_actor?: string`). Attempting to assign this to `updatableData.viewer.actor` will not typecheck!
437
+
438
+ ### Introducing validators
439
+
440
+ The generated file for every generated artifact includes a named `validator` export. In our example, the function is as follows:
441
+
442
+ ```js
443
+ function validate(value/*: {
444
+ +__id: string,
445
+ +__isFoo_actor?: string,
446
+ +$fragmentSpreads: Foo_actor$fragmentType,
447
+ ...
448
+ }*/)/*: false | {
449
+ +__id: string,
450
+ +__isFoo_actor: string,
451
+ +$fragmentSpreads: Foo_actor$fragmentType,
452
+ ...
453
+ }*/ {
454
+ return value.__isFoo_actor != null ? (value/*: any*/) : false;
455
+ }
456
+ ```
457
+
458
+ In other words, this function checks for the presence of the `__isFoo_actor` field. If it is found, it returns the same object, but with a flow type that is valid for assignment. If not, it returns false.
459
+
460
+ ### Example
461
+
462
+ Let's put this all together in an example:
463
+
464
+ ```js
465
+ import {validate as validateActor} from 'Foo_actor.graphql';
466
+
467
+ graphql`
468
+ fragment Foo_actor on Actor @assignable {
469
+ __typename
470
+ }
471
+ `;
472
+
473
+ const data = useFragment(graphql`
474
+ fragment Foo_query on Query {
475
+ node(id: "4") {
476
+ ...Foo_actor
477
+ }
478
+ }
479
+ `, queryRef);
480
+
481
+ const environment = useRelayEnvironment();
482
+ const onClick = () => {
483
+ commitLocalUpdate(environment, store => {
484
+ const {updatableData} = store.readUpdatableQuery(
485
+ graphql`
486
+ query FooUpdatableQuery @updatable {
487
+ viewer {
488
+ actor {
489
+ ...Foo_actor
490
+ }
491
+ }
492
+ }
493
+ `,
494
+ {}
495
+ );
496
+
497
+ if (updatableData.viewer != null && data.node != null) {
498
+ const validActor = validateActor(data.node);
499
+ if (validActor !== false) {
500
+ updatableData.viewer.actor = validActor;
501
+ }
502
+ }
503
+ });
504
+ };
505
+ ```
506
+
507
+ ### Can flow be used to infer the presence of this field?
508
+
509
+ Unfortunately, if you check for the presence of `__isFoo_actor`, Flow does not infer that (on the type level), the field is not optional. Hence, we need to use validators.
510
+
511
+ <DocsRating />
@@ -0,0 +1,142 @@
1
+ ---
2
+ id: imperatively-modifying-store-data-unsafe
3
+ title: Imperatively modifying store data (unsafe)
4
+ slug: /guided-tour/updating-data/imperatively-modifying-store-data-unsafe/
5
+ description: Imperatively modifying store data
6
+ keywords:
7
+ - record source
8
+ - store
9
+ - updater
10
+ ---
11
+
12
+ import DocsRating from '@site/src/core/DocsRating';
13
+ import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
14
+
15
+ Data in Relay stores can be imperatively modified within updater functions.
16
+
17
+ ## When to use updaters
18
+
19
+ ### Complex client updates
20
+
21
+ You might provide an updater function if the changes to local data are more complex than what can be achieved by simply writing a network response to the store and cannot be handled by the declarative mutation directives.
22
+
23
+ ### Client schema extensions
24
+
25
+ In addition, since the network response necessarily will not include data for fields defined in client schema extensions, you may wish to use an updater to initialize data defined in client schema extensions.
26
+
27
+ ### Use of other APIs
28
+
29
+ Lastly, there are things you can only achieve using updaters, such as invalidating nodes, deleting nodes, finding all connections at a given field, etc.
30
+
31
+ ### If multiple optimistic responses modify a given store value
32
+
33
+ If two optimistic responses affect a given value, and the first optimistic response is rolled back, the second one will remain applied.
34
+
35
+ For example, if two optimistic responses each increase a story's like count by one, and the first optimistic response is rolled back, the second optimistic response remains applied. Since the second optimistic response **not recalculated**, the value of the like count will remain increased by two.
36
+
37
+ An optimistic updater, on the other hand, would be re-run in this circumstance.
38
+
39
+ ## When **not** to use updaters
40
+
41
+ ### To trigger other side effects
42
+
43
+ You should use the `onCompleted` callback to trigger other side effects.
44
+
45
+ ## The various types of updater functions
46
+
47
+ The `useMutation` and `commitMutation` APIs accept configuration objects which can include `optimisticUpdater` and `updater` fields. The `requestSubscription` and `useSubscription` APIs accept configuration objects which can include `updater` fields.
48
+
49
+ In addition, there is another API (`commitLocalUpdate`) which also accepts an updater function. It will be discussed in the [Other APIs for modifying local data](../local-data-updates/) section.
50
+
51
+ ## Optimistic updaters vs updaters
52
+
53
+ Mutations can have both optimistic and regular updaters. Optimistic updaters are executed when a mutation is triggered. When that mutation completes or errors, the optimistic update is rolled back. At that point, the mutation response is written to the store and regular updaters are executed. See [order of execution of updater functions](../graphql-mutations/#order-of-execution-of-updater-functions).
54
+
55
+ Regular updaters are executed when a mutation completes successfully.
56
+
57
+ ## Example
58
+
59
+ Let's consider an example that provides an updater to `commitMutation`.
60
+
61
+ ```js
62
+ import type {Environment} from 'react-relay';
63
+ import type {CommentCreateData, CreateCommentMutation} from 'CreateCommentMutation.graphql';
64
+
65
+ const {commitMutation, graphql} = require('react-relay');
66
+ const {ConnectionHandler} = require('relay-runtime');
67
+
68
+ function commitCommentCreateMutation(
69
+ environment: Environment,
70
+ feedbackID: string,
71
+ input: CommentCreateData,
72
+ ) {
73
+ return commitMutation<CreateCommentMutation>(environment, {
74
+ mutation: graphql`
75
+ mutation CreateCommentMutation($input: CommentCreateData!) {
76
+ comment_create(input: $input) {
77
+ comment_edge {
78
+ cursor
79
+ node {
80
+ body {
81
+ text
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ `,
88
+ variables: {input},
89
+ updater: (store: RecordSourceSelectorProxy, _response: ?CreateCommentMutation$data) => {
90
+ // we are not using _response in this example, but it is
91
+ // provided and statically typed.
92
+
93
+ const feedbackRecord = store.get(feedbackID);
94
+
95
+ // Get connection record
96
+ const connectionRecord = ConnectionHandler.getConnection(
97
+ feedbackRecord,
98
+ 'CommentsComponent_comments_connection',
99
+ );
100
+
101
+ // Get the payload returned from the server
102
+ const payload = store.getRootField('comment_create');
103
+
104
+ // Get the edge inside the payload
105
+ const serverEdge = payload.getLinkedRecord('comment_edge');
106
+
107
+ // Build edge for adding to the connection
108
+ const newEdge = ConnectionHandler.buildConnectionEdge(
109
+ store,
110
+ connectionRecord,
111
+ serverEdge,
112
+ );
113
+
114
+ // Add edge to the end of the connection
115
+ ConnectionHandler.insertEdgeAfter(
116
+ connectionRecord,
117
+ newEdge,
118
+ );
119
+ },
120
+ });
121
+ }
122
+
123
+ module.exports = {commit: commitCommentCreateMutation};
124
+ ```
125
+
126
+ Let's distill this example:
127
+
128
+ * The updater receives a `store` argument, which is an instance of a [`RecordSourceSelectorProxy`](../../../api-reference/store/); this interface allows you to *imperatively* write and read data directly to and from the Relay store. This means that you have full control over how to update the store in response to the mutation response: you can *create entirely new records*, or *update or delete existing ones*.
129
+ * The updater receives a second `data` argument, which contains the data selected by the mutation fragment. This can be used to retrieve the payload data without interacting with the *`store`*. The type of this mutation response can be imported from the auto-generated `Mutation.graphql.js` file, and is given the name `MutationName$data`.
130
+ * The type of this `data` argument is a nullable version of the `$data` type.
131
+ * The `data` arguments contains just the data selected directly by the mutation argument. In other words, if another fragment is spread in the mutation, the data from that fragment will not be available within `data` by default.
132
+ * In our specific example, we're adding a new comment to our local store after it has successfully been added on the server. Specifically, we're adding a new item to a connection; for more details on the specifics of how that works, check out our section on [adding and removing items from a connection](../../list-data/updating-connections/).
133
+ * There is no need for an updater in this example — it would be a great place to use the `@appendEdge` directive instead!
134
+ * Note that the mutation response is a *root field* record that can be read from the `store` using the `store.getRootField` API. In our case, we're reading the `comment_create` root field, which is a root field in the mutation response.
135
+ * Note that the `root` field of the mutation is different from the `root` of queries, and `store.getRootField` in the mutation updater can only get the record from the mutation response. To get records from the root that's not in the mutation response, use `store.getRoot().getLinkedRecord` instead.
136
+ * Once the updater completes, any local data updates caused by the mutation `updater` will automatically cause components subscribed to the data to be notified of the change and re-render.
137
+
138
+ ## Learn more
139
+
140
+ See the full APIs [here](../../../api-reference/store/).
141
+
142
+ <DocsRating />