relay-runtime 20.1.1 → 21.0.1
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.
- package/experimental.d.ts +34 -0
- package/experimental.js +1 -1
- package/experimental.js.flow +11 -11
- package/handlers/RelayDefaultHandlerProvider.d.ts +12 -0
- package/handlers/connection/ConnectionHandler.d.ts +51 -0
- package/handlers/connection/ConnectionHandler.js.flow +5 -5
- package/handlers/connection/ConnectionInterface.d.ts +40 -0
- package/handlers/connection/ConnectionInterface.js.flow +1 -1
- package/handlers/connection/MutationHandlers.d.ts +17 -0
- package/index.d.ts +274 -0
- package/index.js +1 -1
- package/index.js.flow +125 -62
- package/lib/experimental.js +3 -3
- package/lib/index.js +105 -57
- package/lib/multi-actor-environment/ActorIdentifier.js +2 -2
- package/lib/multi-actor-environment/MultiActorEnvironment.js +3 -1
- package/lib/mutations/commitMutation.js +8 -8
- package/lib/mutations/validateMutation.js +4 -4
- package/lib/query/GraphQLTag.js +3 -3
- package/lib/query/fetchQuery.js +15 -3
- package/lib/store/DataChecker.js +38 -4
- package/lib/store/NormalizationEngine.js +373 -0
- package/lib/store/OperationExecutor.js +172 -113
- package/lib/store/RelayConcreteVariables.js +1 -1
- package/lib/store/RelayErrorTrie.js +2 -2
- package/lib/store/RelayExperimentalGraphResponseTransform.js +8 -8
- package/lib/store/RelayModernEnvironment.js +26 -19
- package/lib/store/RelayModernRecord.js +18 -8
- package/lib/store/RelayModernSelector.js +9 -9
- package/lib/store/RelayModernStore.js +152 -43
- package/lib/store/RelayPublishQueue.js +1 -1
- package/lib/store/RelayReader.js +76 -38
- package/lib/store/RelayRecordSource.js +6 -0
- package/lib/store/RelayReferenceMarker.js +2 -1
- package/lib/store/RelayResponseNormalizer.js +88 -55
- package/lib/store/RelayStoreSubscriptions.js +34 -10
- package/lib/store/RelayStoreUtils.js +8 -1
- package/lib/store/ResolverFragments.js +2 -2
- package/lib/store/live-resolvers/LiveResolverCache.js +25 -9
- package/lib/store/observeFragmentExperimental.js +17 -1
- package/lib/store/observeQueryExperimental.js +2 -2
- package/lib/subscription/requestSubscription.js +3 -3
- package/lib/util/RelayError.js +3 -0
- package/lib/util/RelayFeatureFlags.js +6 -2
- package/lib/util/RelayReplaySubject.js +4 -4
- package/lib/util/handlePotentialSnapshotErrors.js +2 -2
- package/lib/util/stableCopy.js +2 -2
- package/llm-docs/api-reference/entrypoint-apis/entrypoint-container.mdx +38 -0
- package/llm-docs/api-reference/entrypoint-apis/load-entrypoint.mdx +77 -0
- package/llm-docs/api-reference/entrypoint-apis/use-entrypoint-loader.mdx +99 -0
- package/llm-docs/api-reference/graphql/graphql-directives.mdx +378 -0
- package/llm-docs/api-reference/hooks/_use-lazy-load-query-extra.mdx +16 -0
- package/llm-docs/api-reference/hooks/load-query.mdx +84 -0
- package/llm-docs/api-reference/hooks/relay-environment-provider.mdx +78 -0
- package/llm-docs/api-reference/hooks/use-client-query.mdx +65 -0
- package/llm-docs/api-reference/hooks/use-fragment.mdx +69 -0
- package/llm-docs/api-reference/hooks/use-lazy-load-query.mdx +62 -0
- package/llm-docs/api-reference/hooks/use-mutation.mdx +94 -0
- package/llm-docs/api-reference/hooks/use-pagination-fragment.mdx +166 -0
- package/llm-docs/api-reference/hooks/use-prefetchable-forward-pagination-fragment.mdx +134 -0
- package/llm-docs/api-reference/hooks/use-preloaded-query.mdx +84 -0
- package/llm-docs/api-reference/hooks/use-query-loader.mdx +95 -0
- package/llm-docs/api-reference/hooks/use-refetchable-fragment.mdx +122 -0
- package/llm-docs/api-reference/hooks/use-relay-environment.mdx +37 -0
- package/llm-docs/api-reference/hooks/use-subscription.mdx +66 -0
- package/llm-docs/api-reference/relay-resolvers/docblock-format.mdx +321 -0
- package/llm-docs/api-reference/relay-resolvers/runtime-functions.mdx +94 -0
- package/llm-docs/api-reference/relay-runtime/commit-mutation.mdx +65 -0
- package/llm-docs/api-reference/relay-runtime/fetch-query.mdx +118 -0
- package/llm-docs/api-reference/relay-runtime/field-logger.mdx +170 -0
- package/llm-docs/api-reference/relay-runtime/observe-fragment.mdx +92 -0
- package/llm-docs/api-reference/relay-runtime/relay-environment.mdx +53 -0
- package/llm-docs/api-reference/relay-runtime/request-subscription.mdx +54 -0
- package/llm-docs/api-reference/relay-runtime/runtime-configuration.mdx +52 -0
- package/llm-docs/api-reference/relay-runtime/store.mdx +734 -0
- package/llm-docs/api-reference/relay-runtime/wait-for-fragment-data.mdx +89 -0
- package/llm-docs/api-reference/types/CacheConfig.mdx +8 -0
- package/llm-docs/api-reference/types/Disposable.mdx +4 -0
- package/llm-docs/api-reference/types/GraphQLSubscriptionConfig.mdx +17 -0
- package/llm-docs/api-reference/types/MutationConfig.mdx +31 -0
- package/llm-docs/api-reference/types/SelectorStoreUpdater.mdx +6 -0
- package/llm-docs/api-reference/types/UploadableMap.mdx +3 -0
- package/llm-docs/community/learning-resources.mdx +64 -0
- package/llm-docs/debugging/declarative-mutation-directives.mdx +34 -0
- package/llm-docs/debugging/disallowed-id-types-error.mdx +43 -0
- package/llm-docs/debugging/inconsistent-typename-error.mdx +47 -0
- package/llm-docs/debugging/relay-devtools.mdx +73 -0
- package/llm-docs/debugging/why-null.mdx +116 -0
- package/llm-docs/editor-support.mdx +55 -0
- package/llm-docs/error-reference/unknown-field.mdx +36 -0
- package/llm-docs/getting-started/babel-plugin.mdx +31 -0
- package/llm-docs/getting-started/compiler-config.mdx +25 -0
- package/llm-docs/getting-started/compiler.mdx +98 -0
- package/llm-docs/getting-started/lint-rules.mdx +87 -0
- package/llm-docs/getting-started/production.mdx +30 -0
- package/llm-docs/getting-started/quick-start.mdx +216 -0
- package/llm-docs/glossary/glossary.mdx +1040 -0
- package/llm-docs/guided-tour/list-data/advanced-pagination.mdx +157 -0
- package/llm-docs/guided-tour/list-data/connections.mdx +81 -0
- package/llm-docs/guided-tour/list-data/pagination.mdx +193 -0
- package/llm-docs/guided-tour/list-data/rendering-connections.mdx +112 -0
- package/llm-docs/guided-tour/list-data/streaming-pagination.mdx +87 -0
- package/llm-docs/guided-tour/managing-data-outside-react/retaining-queries.mdx +51 -0
- package/llm-docs/guided-tour/refetching/refetching-queries-with-different-data.mdx +337 -0
- package/llm-docs/guided-tour/refetching/refreshing-queries.mdx +350 -0
- package/llm-docs/guided-tour/rendering/environment.mdx +59 -0
- package/llm-docs/guided-tour/rendering/error-states.mdx +295 -0
- package/llm-docs/guided-tour/rendering/fragments.mdx +354 -0
- package/llm-docs/guided-tour/rendering/loading-states.mdx +245 -0
- package/llm-docs/guided-tour/rendering/queries.mdx +261 -0
- package/llm-docs/guided-tour/rendering/variables.mdx +233 -0
- package/llm-docs/guided-tour/reusing-cached-data/fetch-policies.mdx +56 -0
- package/llm-docs/guided-tour/reusing-cached-data/filling-in-missing-data.mdx +102 -0
- package/llm-docs/guided-tour/reusing-cached-data/introduction.mdx +22 -0
- package/llm-docs/guided-tour/reusing-cached-data/presence-of-data.mdx +93 -0
- package/llm-docs/guided-tour/reusing-cached-data/rendering-partially-cached-data.mdx +175 -0
- package/llm-docs/guided-tour/reusing-cached-data/staleness-of-data.mdx +116 -0
- package/llm-docs/guided-tour/updating-data/client-only-data.mdx +115 -0
- package/llm-docs/guided-tour/updating-data/graphql-mutations.mdx +334 -0
- package/llm-docs/guided-tour/updating-data/graphql-subscriptions.mdx +279 -0
- package/llm-docs/guided-tour/updating-data/imperatively-modifying-linked-fields.mdx +511 -0
- package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data-legacy.mdx +142 -0
- package/llm-docs/guided-tour/updating-data/imperatively-modifying-store-data.mdx +275 -0
- package/llm-docs/guided-tour/updating-data/introduction.mdx +25 -0
- package/llm-docs/guided-tour/updating-data/local-data-updates.mdx +71 -0
- package/llm-docs/guided-tour/updating-data/typesafe-updaters-faq.mdx +83 -0
- package/llm-docs/guided-tour/updating-data/updating-connections.mdx +592 -0
- package/llm-docs/guides/alias-directive.mdx +160 -0
- package/llm-docs/guides/catch-directive.mdx +167 -0
- package/llm-docs/guides/client-schema-extensions.mdx +208 -0
- package/llm-docs/guides/codemods.mdx +79 -0
- package/llm-docs/guides/data-driven-dependencies/client-3d.mdx +255 -0
- package/llm-docs/guides/data-driven-dependencies/configuration.mdx +127 -0
- package/llm-docs/guides/data-driven-dependencies/introduction.mdx +39 -0
- package/llm-docs/guides/data-driven-dependencies/server-3d.mdx +664 -0
- package/llm-docs/guides/document-comparison.mdx +106 -0
- package/llm-docs/guides/graphql-server-specification.mdx +453 -0
- package/llm-docs/guides/network-layer.mdx +69 -0
- package/llm-docs/guides/persisted-queries.mdx +328 -0
- package/llm-docs/guides/relay-resolvers/context.mdx +99 -0
- package/llm-docs/guides/relay-resolvers/defining-fields.mdx +151 -0
- package/llm-docs/guides/relay-resolvers/defining-types.mdx +164 -0
- package/llm-docs/guides/relay-resolvers/deprecated.mdx +27 -0
- package/llm-docs/guides/relay-resolvers/derived-fields.mdx +127 -0
- package/llm-docs/guides/relay-resolvers/descriptions.mdx +44 -0
- package/llm-docs/guides/relay-resolvers/enabling.mdx +41 -0
- package/llm-docs/guides/relay-resolvers/errors.mdx +64 -0
- package/llm-docs/guides/relay-resolvers/field-arguments.mdx +63 -0
- package/llm-docs/guides/relay-resolvers/introduction.mdx +62 -0
- package/llm-docs/guides/relay-resolvers/limitations.mdx +30 -0
- package/llm-docs/guides/relay-resolvers/live-fields.mdx +164 -0
- package/llm-docs/guides/relay-resolvers/return-types.mdx +161 -0
- package/llm-docs/guides/relay-resolvers/suspense.mdx +41 -0
- package/llm-docs/guides/required-directive.mdx +240 -0
- package/llm-docs/guides/semantic-nullability.mdx +93 -0
- package/llm-docs/guides/testing-relay-components.mdx +642 -0
- package/llm-docs/guides/testing-relay-with-preloaded-queries.mdx +160 -0
- package/llm-docs/guides/throw-on-field-error-directive.mdx +58 -0
- package/llm-docs/guides/type-emission.mdx +414 -0
- package/llm-docs/home.mdx +32 -0
- package/llm-docs/principles-and-architecture/architecture-overview.mdx +24 -0
- package/llm-docs/principles-and-architecture/compiler-architecture.mdx +106 -0
- package/llm-docs/principles-and-architecture/runtime-architecture.mdx +249 -0
- package/llm-docs/principles-and-architecture/thinking-in-graphql.mdx +309 -0
- package/llm-docs/principles-and-architecture/thinking-in-relay.mdx +104 -0
- package/llm-docs/principles-and-architecture/videos.mdx +50 -0
- package/llm-docs/tutorial/arrays-lists.mdx +126 -0
- package/llm-docs/tutorial/fragments-1.mdx +487 -0
- package/llm-docs/tutorial/graphql.mdx +172 -0
- package/llm-docs/tutorial/interfaces-polymorphism.mdx +161 -0
- package/llm-docs/tutorial/intro.mdx +58 -0
- package/llm-docs/tutorial/mutations-updates.mdx +624 -0
- package/llm-docs/tutorial/organizing-mutations-queries-and-subscriptions.mdx +13 -0
- package/llm-docs/tutorial/queries-1.mdx +267 -0
- package/llm-docs/tutorial/queries-2.mdx +389 -0
- package/llm-docs/tutorial/refetchable-fragments.mdx +352 -0
- package/multi-actor-environment/ActorIdentifier.d.ts +17 -0
- package/multi-actor-environment/ActorIdentifier.js.flow +2 -2
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +15 -15
- package/multi-actor-environment/ActorUtils.js.flow +1 -1
- package/multi-actor-environment/MultiActorEnvironment.d.ts +123 -0
- package/multi-actor-environment/MultiActorEnvironment.js.flow +32 -24
- package/multi-actor-environment/MultiActorEnvironmentTypes.d.ts +225 -0
- package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +6 -6
- package/multi-actor-environment/index.d.ts +14 -0
- package/multi-actor-environment.d.ts +8 -0
- package/mutations/RelayDeclarativeMutationConfig.d.ts +70 -0
- package/mutations/RelayDeclarativeMutationConfig.js.flow +9 -9
- package/mutations/RelayRecordProxy.js.flow +8 -11
- package/mutations/RelayRecordSourceMutator.js.flow +4 -4
- package/mutations/RelayRecordSourceProxy.js.flow +4 -4
- package/mutations/RelayRecordSourceSelectorProxy.js.flow +6 -6
- package/mutations/applyOptimisticMutation.d.ts +25 -0
- package/mutations/applyOptimisticMutation.js.flow +2 -2
- package/mutations/commitLocalUpdate.d.ts +10 -0
- package/mutations/commitMutation.d.ts +48 -0
- package/mutations/commitMutation.js.flow +21 -17
- package/mutations/createUpdatableProxy.js.flow +19 -19
- package/mutations/readUpdatableFragment.js.flow +3 -3
- package/mutations/readUpdatableQuery.js.flow +3 -3
- package/mutations/validateMutation.js.flow +7 -7
- package/network/RelayNetwork.d.ts +12 -0
- package/network/RelayNetworkTypes.d.ts +145 -0
- package/network/RelayNetworkTypes.js.flow +18 -18
- package/network/RelayObservable.d.ts +197 -0
- package/network/RelayObservable.js.flow +32 -30
- package/network/RelayQueryResponseCache.d.ts +16 -0
- package/network/RelayQueryResponseCache.js.flow +3 -3
- package/network/wrapNetworkWithLogObserver.js.flow +1 -1
- package/package.json +2 -1
- package/query/GraphQLTag.d.ts +45 -0
- package/query/GraphQLTag.js.flow +22 -10
- package/query/fetchQuery.d.ts +21 -0
- package/query/fetchQuery.js.flow +23 -10
- package/query/fetchQueryInternal.d.ts +26 -0
- package/query/fetchQueryInternal.js.flow +4 -4
- package/query/fetchQuery_DEPRECATED.d.ts +17 -0
- package/query/fetchQuery_DEPRECATED.js.flow +1 -1
- package/store/ClientID.d.ts +14 -0
- package/store/DataChecker.js.flow +51 -15
- package/store/NormalizationEngine.js.flow +782 -0
- package/store/OperationExecutor.d.ts +51 -0
- package/store/OperationExecutor.js.flow +204 -98
- package/store/RelayConcreteVariables.js.flow +5 -5
- package/store/RelayErrorTrie.js.flow +12 -12
- package/store/RelayExperimentalGraphResponseHandler.js.flow +3 -3
- package/store/RelayExperimentalGraphResponseTransform.js.flow +10 -10
- package/store/RelayModernEnvironment.d.ts +97 -0
- package/store/RelayModernEnvironment.js.flow +58 -43
- package/store/RelayModernFragmentSpecResolver.js.flow +1 -1
- package/store/RelayModernOperationDescriptor.d.ts +28 -0
- package/store/RelayModernOperationDescriptor.js.flow +1 -1
- package/store/RelayModernRecord.d.ts +92 -0
- package/store/RelayModernRecord.js.flow +44 -20
- package/store/RelayModernSelector.d.ts +123 -0
- package/store/RelayModernSelector.js.flow +21 -21
- package/store/RelayModernStore.d.ts +57 -0
- package/store/RelayModernStore.js.flow +219 -58
- package/store/RelayOperationTracker.d.ts +29 -0
- package/store/RelayOperationTracker.js.flow +2 -2
- package/store/RelayOptimisticRecordSource.js.flow +2 -2
- package/store/RelayPublishQueue.js.flow +29 -20
- package/store/RelayReader.js.flow +129 -57
- package/store/RelayRecordSource.d.ts +26 -0
- package/store/RelayRecordSource.js.flow +10 -0
- package/store/RelayRecordState.d.ts +28 -0
- package/store/RelayRecordState.js.flow +1 -1
- package/store/RelayReferenceMarker.js.flow +5 -4
- package/store/RelayResponseNormalizer.d.ts +28 -0
- package/store/RelayResponseNormalizer.js.flow +130 -62
- package/store/RelayStoreSubscriptions.js.flow +52 -8
- package/store/RelayStoreTypes.d.ts +1327 -0
- package/store/RelayStoreTypes.js.flow +371 -278
- package/store/RelayStoreUtils.d.ts +86 -0
- package/store/RelayStoreUtils.js.flow +16 -8
- package/store/ResolverCache.js.flow +2 -2
- package/store/ResolverFragments.d.ts +43 -0
- package/store/ResolverFragments.js.flow +22 -14
- package/store/StoreInspector.js.flow +7 -8
- package/store/ViewerPattern.d.ts +11 -0
- package/store/cloneRelayHandleSourceField.js.flow +1 -1
- package/store/cloneRelayScalarHandleSourceField.js.flow +1 -1
- package/store/createFragmentSpecResolver.d.ts +16 -0
- package/store/createRelayContext.js.flow +1 -1
- package/store/createRelayLoggingContext.js.flow +4 -4
- package/store/defaultGetDataID.js.flow +2 -2
- package/store/isRelayModernEnvironment.d.ts +8 -0
- package/store/isRelayModernEnvironment.js.flow +4 -2
- package/store/live-resolvers/LiveResolverCache.js.flow +55 -20
- package/store/live-resolvers/LiveResolverSuspenseSentinel.js.flow +3 -3
- package/store/live-resolvers/getOutputTypeRecordIDs.js.flow +1 -1
- package/store/live-resolvers/isLiveStateValue.js.flow +2 -2
- package/store/live-resolvers/resolverDataInjector.d.ts +27 -0
- package/store/live-resolvers/resolverDataInjector.js.flow +8 -5
- package/store/observeFragmentExperimental.d.ts +46 -0
- package/store/observeFragmentExperimental.js.flow +50 -21
- package/store/observeQueryExperimental.d.ts +30 -0
- package/store/observeQueryExperimental.js.flow +5 -5
- package/store/readInlineData.d.ts +19 -0
- package/store/readInlineData.js.flow +5 -5
- package/store/waitForFragmentExperimental.d.ts +49 -0
- package/store/waitForFragmentExperimental.js.flow +3 -3
- package/subscription/requestSubscription.d.ts +27 -0
- package/subscription/requestSubscription.js.flow +10 -10
- package/util/JSResourceTypes.flow.js.flow +4 -4
- package/util/NormalizationNode.d.ts +235 -0
- package/util/NormalizationNode.js.flow +127 -123
- package/util/ReaderNode.d.ts +264 -0
- package/util/ReaderNode.js.flow +156 -151
- package/util/RelayConcreteNode.d.ts +120 -0
- package/util/RelayConcreteNode.js.flow +32 -32
- package/util/RelayError.d.ts +13 -0
- package/util/RelayError.js.flow +4 -1
- package/util/RelayFeatureFlags.d.ts +40 -0
- package/util/RelayFeatureFlags.js.flow +21 -1
- package/util/RelayProfiler.d.ts +121 -0
- package/util/RelayProfiler.js.flow +1 -1
- package/util/RelayReplaySubject.d.ts +25 -0
- package/util/RelayReplaySubject.js.flow +3 -3
- package/util/RelayRuntimeTypes.d.ts +59 -0
- package/util/RelayRuntimeTypes.js.flow +36 -33
- package/util/createPayloadFor3DField.d.ts +17 -0
- package/util/createPayloadFor3DField.js.flow +9 -5
- package/util/deepFreeze.d.ts +8 -0
- package/util/deepFreeze.js.flow +2 -2
- package/util/getFragmentIdentifier.d.ts +10 -0
- package/util/getFragmentIdentifier.js.flow +1 -1
- package/util/getPaginationMetadata.d.ts +20 -0
- package/util/getPaginationMetadata.js.flow +1 -1
- package/util/getPaginationVariables.d.ts +20 -0
- package/util/getPaginationVariables.js.flow +1 -1
- package/util/getPendingOperationsForFragment.d.ts +18 -0
- package/util/getPendingOperationsForFragment.js.flow +2 -2
- package/util/getRefetchMetadata.d.ts +19 -0
- package/util/getRefetchMetadata.js.flow +6 -5
- package/util/getRelayHandleKey.d.ts +8 -0
- package/util/getRequestIdentifier.d.ts +17 -0
- package/util/getValueAtPath.d.ts +8 -0
- package/util/getValueAtPath.js.flow +3 -3
- package/util/handlePotentialSnapshotErrors.d.ts +14 -0
- package/util/handlePotentialSnapshotErrors.js.flow +5 -5
- package/util/isEmptyObject.js.flow +1 -1
- package/util/isPromise.d.ts +8 -0
- package/util/isPromise.js.flow +2 -2
- package/util/isScalarAndEqual.d.ts +8 -0
- package/util/isScalarAndEqual.js.flow +1 -1
- package/util/recycleNodesInto.d.ts +8 -0
- package/util/recycleNodesInto.js.flow +2 -2
- package/util/registerEnvironmentWithDevTools.js.flow +1 -1
- package/util/shallowFreeze.js.flow +1 -1
- package/util/stableCopy.d.ts +8 -0
- package/util/stableCopy.js.flow +5 -5
- package/util/withProvidedVariables.d.ts +19 -0
- 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 />
|