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,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: live-fields
|
|
3
|
+
title: "Live Fields"
|
|
4
|
+
slug: /guides/relay-resolvers/live-fields/
|
|
5
|
+
description: Modeling data that changes over time in Relay Resolvers
|
|
6
|
+
---
|
|
7
|
+
import {FbInternalOnly, fbContent} from 'docusaurus-plugin-internaldocs-fb/internal';
|
|
8
|
+
import Tabs from '@theme/Tabs';
|
|
9
|
+
import TabItem from '@theme/TabItem';
|
|
10
|
+
|
|
11
|
+
One critical difference between client state and server state is that as client state changes over time, those changes will need to be reflected in your UI. To address this, Relay Resolvers support the ability to be marked as `@live`. Live resolvers are expected to return a `LiveState` shaped object which includes methods which allow Relay to both `read()` the current value and also to `subscribe()` to changes to the value.
|
|
12
|
+
|
|
13
|
+
As this value changes over time, Relay will automatically recompute any [derived fields](./derived-fields.mdx) that depend on this field (including transitive dependencies if the changes cascade), and also efficiently trigger the update of any components/subscribers which have read fields that updated as a result of this change.
|
|
14
|
+
|
|
15
|
+
## @live
|
|
16
|
+
|
|
17
|
+
<Tabs
|
|
18
|
+
groupId="resolver"
|
|
19
|
+
defaultValue="Docblock"
|
|
20
|
+
values={fbContent({
|
|
21
|
+
internal: [
|
|
22
|
+
{label: 'Docblock', value: 'Docblock'},
|
|
23
|
+
{label: 'Flow', value: 'Flow'},
|
|
24
|
+
],
|
|
25
|
+
external: [
|
|
26
|
+
{label: 'Docblock', value: 'Docblock'},
|
|
27
|
+
]
|
|
28
|
+
})}>
|
|
29
|
+
<TabItem value="Docblock">
|
|
30
|
+
|
|
31
|
+
To mark a resolver as live, add the `@live` docblock tag to the resolver definition. For example:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import type { LiveState } from 'relay-runtime';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @relayField Query.counter: Int
|
|
38
|
+
* @live
|
|
39
|
+
*/
|
|
40
|
+
export function counter(): LiveState<number> {
|
|
41
|
+
return {
|
|
42
|
+
read: () => store.getState().counter,
|
|
43
|
+
subscribe: (callback) => {
|
|
44
|
+
return store.subscribe(callback);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
</TabItem>
|
|
51
|
+
|
|
52
|
+
<TabItem value="Flow">
|
|
53
|
+
<FbInternalOnly>
|
|
54
|
+
|
|
55
|
+
live is determined by the usage of `LiveState` in the Flow return type
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import type { LiveState } from 'relay-runtime';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @relayField
|
|
62
|
+
*/
|
|
63
|
+
export function counter(): LiveState<number> {
|
|
64
|
+
return {
|
|
65
|
+
read: () => store.getState().counter,
|
|
66
|
+
subscribe: (callback) => {
|
|
67
|
+
return store.subscribe(callback);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
</FbInternalOnly>
|
|
74
|
+
</TabItem>
|
|
75
|
+
</Tabs>
|
|
76
|
+
|
|
77
|
+
:::note
|
|
78
|
+
Both field resolvers and strong model resolvers, which map an ID to a model, may be annotated as `@live`.
|
|
79
|
+
:::
|
|
80
|
+
|
|
81
|
+
## The LiveState Type
|
|
82
|
+
|
|
83
|
+
The return type of a Live Resolver is known as a `LiveState`. It is conceptually similar to an observable or a signal, if you are familiar with those concepts. Unlike an observable, when a `LiveState` notifies its subscriber of an update, it does not include the new value. Instead, the subscriber (Relay) is expected to call `read()` to get the new value.
|
|
84
|
+
|
|
85
|
+
While over-notification (subscription notifications when the read value has not actually changed) is supported, for performance reasons, it is recommended that the provider of the LiveState value confirms that the value has indeed changed before notifying Relay of the change.
|
|
86
|
+
|
|
87
|
+
The type of a LiveState is defined as follows:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
export type LiveState<T> = {
|
|
91
|
+
/**
|
|
92
|
+
* Returns the current value of the live state.
|
|
93
|
+
*/
|
|
94
|
+
read(): T,
|
|
95
|
+
/**
|
|
96
|
+
* Subscribes to changes in the live state. The state provider should
|
|
97
|
+
* call the callback when the value of the live state changes.
|
|
98
|
+
*/
|
|
99
|
+
subscribe(cb: () => void): () => void,
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Creating a LiveState Object
|
|
104
|
+
|
|
105
|
+
In most cases, you will want to define a helper function that reads your reactive data store and returns a `LiveState` object. For example, you for a Redux store you might write a wrapper that exposes a `LiveState` for a given selector:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
type Selector<T> = (state: State) => T;
|
|
109
|
+
|
|
110
|
+
function selectorAsLiveState<T>(selector: Selector<T>): LiveState<T> {
|
|
111
|
+
let currentValue = selector(store.getState());
|
|
112
|
+
return {
|
|
113
|
+
read: () => currentValue,
|
|
114
|
+
subscribe: (cb) => {
|
|
115
|
+
return store.subscribe(() => {
|
|
116
|
+
const newValue = selector(store.getState());
|
|
117
|
+
if (newValue === currentValue) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
currentValue = newValue;
|
|
121
|
+
cb();
|
|
122
|
+
});
|
|
123
|
+
return unsubscribe;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
A Live Resolver that uses this helper might look like this:
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
/**
|
|
133
|
+
* @relayField Query.counter: Int
|
|
134
|
+
* @live
|
|
135
|
+
*/
|
|
136
|
+
export function counter(): LiveState<number> {
|
|
137
|
+
return selectorAsLiveState(getCounter);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getCounter(state) {
|
|
141
|
+
return state.counter;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Batching
|
|
146
|
+
|
|
147
|
+
When state changes in your data layer, it's possible that one change could result in notifying many `@live` resolver subscriptions about updates. By default each of these updates will require Relay to do work to determine which components need to be updated. This can lead to significant duplicate work being performed.
|
|
148
|
+
|
|
149
|
+
When possible, it is recommended that you batch updates to `@live` resolvers. This can be done by wrapping your state updates in a `batchLiveStateUpdates()` call on your `RelayStore` instance.
|
|
150
|
+
|
|
151
|
+
A typical use with a Redux store might look like this:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const store = createStore(reducer);
|
|
155
|
+
const originalDispatch = store.dispatch;
|
|
156
|
+
|
|
157
|
+
function wrapped(action) {
|
|
158
|
+
relayStore.batchLiveStateUpdates(() => {
|
|
159
|
+
originalDispatch(action);
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
store.dispatch = wrapped;
|
|
164
|
+
```
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: return-types
|
|
3
|
+
title: "Return Types"
|
|
4
|
+
slug: /guides/relay-resolvers/return-types/
|
|
5
|
+
description: Showing the different types of return values for Relay Resolvers
|
|
6
|
+
keywords:
|
|
7
|
+
- resolvers
|
|
8
|
+
- derived
|
|
9
|
+
- selectors
|
|
10
|
+
- reactive
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Relay Resolvers support a number of different return types, each of which has different semantics. This page will walk through the different types of supported return values and how they are used.
|
|
14
|
+
|
|
15
|
+
## Scalar Types
|
|
16
|
+
|
|
17
|
+
The simplest type for a resolver to return is a built-in GraphQL scalar value. Scalar values are values that can be represented as a primitive value in GraphQL, such as a string, number, or boolean. To return a scalar simply define your resolver as returning the scalar type and then return the corresponding JavaScript value from your resolver function.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
/**
|
|
21
|
+
* @relayField Post.isValid: Boolean
|
|
22
|
+
*/
|
|
23
|
+
export function isValid(post: PostModel): boolean {
|
|
24
|
+
return post.content !== "" && post.author != null;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## List Types
|
|
29
|
+
|
|
30
|
+
Resolvers may also return a list of values. To do so, define your resolver as returning a list of the corresponding type and return an array from your resolver function.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
/**
|
|
34
|
+
* @relayField User.favoriteColors: [String]
|
|
35
|
+
*/
|
|
36
|
+
export function favoriteColors(user: UserModel): string[] {
|
|
37
|
+
return user.favoriteColors;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This pattern can be used for the other types, with the exception of server types, which don't yet support lists.
|
|
42
|
+
|
|
43
|
+
## Client-defined GraphQL Types
|
|
44
|
+
|
|
45
|
+
Resolvers can also model edges to other GraphQL types in your Resolver schema. If the type was defined as a "strong" type, the resolver function must return an object `{ id: DataID }` where `DataID` is the ID of the object. Relay will take care of invoking the type's model resolver function.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import {DataID} from 'relay-runtime';
|
|
49
|
+
/**
|
|
50
|
+
* @relayField Post.author: User
|
|
51
|
+
*/
|
|
52
|
+
export function author(post: PostModel): { id: DataID } {
|
|
53
|
+
return { id: post.authorId };
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If the type was defined as `@weak`, the resolver function must return an object matching the type's model type.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
/**
|
|
61
|
+
* @relayField User.profilePicture: ProfilePicture
|
|
62
|
+
*/
|
|
63
|
+
export function profilePicture(user: UserModel): ProfilePicture {
|
|
64
|
+
return {
|
|
65
|
+
url: user.profilePicture.url,
|
|
66
|
+
width: user.profilePicture.width,
|
|
67
|
+
height: user.profilePicture.width,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
:::tip
|
|
73
|
+
Relay will emit type assertions in its generated code to help catch errors where a resolver implementation does not match whats declared in its docblock.
|
|
74
|
+
:::
|
|
75
|
+
|
|
76
|
+
## Server Types
|
|
77
|
+
|
|
78
|
+
Relay Resolvers also support modeling edges to types defined on your server schema that implement the [`Node` specification](https://graphql.org/learn/global-object-identification/#node-root-field). Since objects which implement Node each have a globally unique ID, resolvers modeling edges to these server types simply need to return that unique ID.
|
|
79
|
+
|
|
80
|
+
At compile-time Relay derives a GraphQL query for each selections on this field and will lazily fetch that data on render.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import {DataID} from 'relay-runtime';
|
|
84
|
+
/**
|
|
85
|
+
* @relayField Post.author: User
|
|
86
|
+
*/
|
|
87
|
+
export function author(post: PostModel): DataID {
|
|
88
|
+
return post.authorId;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
:::warning
|
|
93
|
+
Edges to server types that are only known the client force Relay to fetch data lazily which will force an additional cascading network roundtrip. This is generally not optimal and should be avoided where possible.
|
|
94
|
+
:::
|
|
95
|
+
|
|
96
|
+
To highlight this point, at compile time, Relay requires that selection that reads client to server edge field annotate the field with the `@waterfall` directive. This is intended to remind the author and reviewer that a tradeoff is being made here and to carefully consider the implications.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
function Post() {
|
|
100
|
+
const data = useLazyLoadQuery(graphql`
|
|
101
|
+
query PostQuery {
|
|
102
|
+
post {
|
|
103
|
+
author @waterfall {
|
|
104
|
+
name
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}`, {});
|
|
108
|
+
return <p>{data.post.author.name}</p>;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Abstract Types
|
|
113
|
+
|
|
114
|
+
Resolvers may return some permutations of "abstract" types (GraphQL unions and interfaces). To use this feature simply use the abstract type's name in the docblock field description and include the typename in the object returned from your resolver. For "strong" types, that will look like: `{id: DataID, __typename: string}`. For "weak" types that will look like: `{__relay_model_instance: T, __typename: string}`.
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import {DataID} from 'relay-runtime';
|
|
118
|
+
|
|
119
|
+
type AnimalTypenames = "Cat" | "Dog";
|
|
120
|
+
/**
|
|
121
|
+
* @relayField User.pet: Animal
|
|
122
|
+
*/
|
|
123
|
+
export function pet(user: User): {id: DataID, __typename: AnimalTypenames } {
|
|
124
|
+
return {id: "5", __typename: "Dog" }
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
:::tip
|
|
129
|
+
Relay will generate type assertions to ensure your resolver function returns the expected type. However, not all combinations are supported. For example, Relay does not yet support the following permutations of abstract types: Unions including weak types, abstract types which mix strong and weak types, and abstract types which include server-backed types.
|
|
130
|
+
:::
|
|
131
|
+
|
|
132
|
+
While abstract types themselves cannot be defined using Resolver syntax today, you may define interfaces and unions, as well as their members, using [Client Schema Extensions](../client-schema-extensions.mdx). For example:
|
|
133
|
+
|
|
134
|
+
```graphql title="client-schema.graphql"
|
|
135
|
+
interface Animal {
|
|
136
|
+
legs: Int
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
extend type Cat implements Animal {
|
|
140
|
+
__do_not_use: String # Placeholder because GraphQL does not allow empty field sets.
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## JavaScript Values
|
|
145
|
+
|
|
146
|
+
There are rare cases where you want to return an arbitrary JavaScript value from your Resolver schema, one which cannot not have a corresponding GraphQL type. As an escape hatch Relay supports a custom return type `RelayResolverValue` that allows you to return any JavaScript value from your resolver. **JavaScript values returned from resolvers should be immutable.**
|
|
147
|
+
|
|
148
|
+
Consumers of this field will see a TypeScript/Flow type that is derived from your resolver function's return type.
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
/**
|
|
152
|
+
* @relayField Post.publishDate: RelayResolverValue
|
|
153
|
+
*/
|
|
154
|
+
export function metadata(post: PostModel): Date {
|
|
155
|
+
return post.publishDate;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
:::warning
|
|
160
|
+
Use of `RelayResolverValue` should be considered an "escape hatch" and may be deprecated in future versions of Relay. In most cases a preferable pattern is to define a custom scalar in your [client schema extensions](../client-schema-extensions.mdx) and add a type definition for that custom scalar in your Relay config.
|
|
161
|
+
:::
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: suspense
|
|
3
|
+
title: "Suspense"
|
|
4
|
+
slug: /guides/relay-resolvers/suspense/
|
|
5
|
+
description: Handling loading states for live data
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
With [Live Resolvers](./live-fields.mdx), it's possible that the data you are exposing in the graph may not be synchronously available. For example, if you are fetching data from a remote API, it may take some time for the data to be fetched. Relay Resolvers provide a mechanism for handling this loading state.
|
|
9
|
+
|
|
10
|
+
If a Live Resolver returns the "suspense sentinel" value, all consumers of that field will suspend until that field updates with a non-suspense value.
|
|
11
|
+
|
|
12
|
+
## Suspense Sentinel
|
|
13
|
+
|
|
14
|
+
If a Live Resolver is in a loading state, it may return a special sentinel value to indicate that the data is not yet available.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import {suspenseSentinel} from 'relay-runtime';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @relayField Query.myIp: String
|
|
21
|
+
* @live
|
|
22
|
+
*/
|
|
23
|
+
export function myIp(): LiveState<string> {
|
|
24
|
+
return {
|
|
25
|
+
read: () => {
|
|
26
|
+
const state = store.getState();
|
|
27
|
+
const ipLoadObject = state.ip;
|
|
28
|
+
if (ipLoadObject.status === "LOADING") {
|
|
29
|
+
return suspenseSentinel();
|
|
30
|
+
}
|
|
31
|
+
return state.ip;
|
|
32
|
+
},
|
|
33
|
+
subscribe: (cb) => {
|
|
34
|
+
return store.subscribe(cb);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
:::note
|
|
41
|
+
If a query or fragment will suspend if it reads any resolver field that is in a suspended state, even if it reads that resolver field indirectly via another resolvers `@rootFragment`.
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: required-directive
|
|
3
|
+
title: "@required Directive"
|
|
4
|
+
slug: /guides/required-directive/
|
|
5
|
+
description: Relay guide to @required
|
|
6
|
+
keywords:
|
|
7
|
+
- required
|
|
8
|
+
- directive
|
|
9
|
+
- optional
|
|
10
|
+
- nullthrows
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
import DocsRating from '@site/src/core/DocsRating';
|
|
14
|
+
|
|
15
|
+
The `@required` directive can be added to fields in your Relay queries to declare how null values should be handled at runtime. You can think of it as saying "if this field is ever null, its parent field is invalid and should be null".
|
|
16
|
+
|
|
17
|
+
When you have a GraphQL schema where many fields are nullable, a considerable amount of product code is needed to handle each field's potential "nullness" before the underlying data can be used. With `@required`, Relay can handle some types of null checks before it returns data to your component, which means that **any field you annotate with** **`@required`** **will become non-nullable in the generated types for your response**.
|
|
18
|
+
|
|
19
|
+
If a `@required` field is null at runtime, Relay will "bubble" that nullness up to the field's parent. For example, given this query:
|
|
20
|
+
|
|
21
|
+
```graphql
|
|
22
|
+
query MyQuery {
|
|
23
|
+
viewer {
|
|
24
|
+
name @required(action: LOG)
|
|
25
|
+
age
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If `name` is null, Relay would return `{ viewer: null }`. You can think of `@required` in this instance as saying "`viewer` is useless without a `name`".
|
|
31
|
+
|
|
32
|
+
## Action
|
|
33
|
+
|
|
34
|
+
The `@required` directive has a required `action` argument which has four possible values:
|
|
35
|
+
|
|
36
|
+
### `NONE` (expected)
|
|
37
|
+
|
|
38
|
+
This field is expected to be null sometimes. The field's nullability will "bubble up" to make the parent field null if this field is missing, allowing the component to check the parent field instead of this specific field.
|
|
39
|
+
|
|
40
|
+
### `LOG` (recoverable)
|
|
41
|
+
|
|
42
|
+
This value is not expected to ever be null, but the component **can still render** if it is. If a field with `action: LOG` is null, the [Relay field logger](../api-reference/relay-runtime/field-logger.mdx) will receive a `missing_required_field.log` event. The field's nullability will "bubble up" to make the parent field null.
|
|
43
|
+
|
|
44
|
+
### `THROW` (unrecoverable)
|
|
45
|
+
|
|
46
|
+
This value should not be null, and the component **cannot render without it**. If a field with `action: THROW` is null at runtime, the component which reads that field **will throw during render**. The error message includes both the owner and field path. Only use this option if your component is contained within an [error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary).
|
|
47
|
+
|
|
48
|
+
**Note**: If you have opted into the optional `disallow_required_action_throw_on_semantically_nullable_fields` [compiler feature flag](../../docs/getting-started/compiler-config.mdx), this can only be used on fields that are non-nullable (`!` in the schema) or are marked with `@semanticNonNull` (an optional [semantic nullability](./semantic-nullability.mdx) feature that indicates fields should never be null in normal operation).
|
|
49
|
+
|
|
50
|
+
### `DANGEROUSLY_THROW_ON_SEMANTICALLY_NULLABLE_FIELD` (unrecoverable, bypass validation)
|
|
51
|
+
|
|
52
|
+
:::warning
|
|
53
|
+
This option is included mostly as a migration path for existing codebases that used `@required(action: THROW)` on nullable fields. It is strongly discouraged to add new usages of this option.
|
|
54
|
+
:::
|
|
55
|
+
|
|
56
|
+
This action behaves identically to `THROW` but can be used on nullable fields. This is only needed when the optional [compiler feature flag](https://relay.dev/docs/getting-started/compiler-config/#FeatureFlags) `disallow_required_action_throw_on_semantically_nullable_fields` is enabled, which prevents using THROW on semantically nullable fields.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Locality
|
|
60
|
+
|
|
61
|
+
A field's `@required` status is **local to the fragment where it is specified**. This allows you to add/remove the directive without having to think about anything outside the scope of your component.
|
|
62
|
+
|
|
63
|
+
This choice reflects the fact that some components may be able to recover better from missing data than others. For example, a `<RestaurantInfo />` component could probably render something sensible even if the restaurant's address is missing, but a `<RestaurantLocationMap />` component might not.
|
|
64
|
+
|
|
65
|
+
However, all usages of the `@required` directive on the same field in a single fragment must be consistent with their usage. This situation mostly occurs when selecting fields in inline fragments. For example, the following fragment would fail to compile:
|
|
66
|
+
|
|
67
|
+
```graphql
|
|
68
|
+
fragment UserInfo on User {
|
|
69
|
+
job {
|
|
70
|
+
... on Actor {
|
|
71
|
+
certifications
|
|
72
|
+
}
|
|
73
|
+
... on Lawyer {
|
|
74
|
+
certifications @required(action: LOG)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The Relay compiler will give you an error like `All references to a field must have matching @required declarations.`. To fix this, either set the `@required` directive on each of the fields selected in the inline fragment or remove the directive entirely.
|
|
81
|
+
|
|
82
|
+
## Chaining
|
|
83
|
+
|
|
84
|
+
`@required` directives can be chained to make a deeply nested field accessible after just one null check:
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
const user = useFragment(graphql`
|
|
88
|
+
fragment MyUser on User {
|
|
89
|
+
name @required(action: LOG)
|
|
90
|
+
profile_picture @required(action: LOG) {
|
|
91
|
+
url @required(action: LOG)
|
|
92
|
+
}
|
|
93
|
+
}`, key);
|
|
94
|
+
if(user == null) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return <img src={user.profile_picture.url} alt={user.name} />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Note**: If you use `@required` on a top level field of a fragment, the object returned from `useFragment` itself may become nullable. The generated types will reflect this.
|
|
101
|
+
|
|
102
|
+
When chaining `@required` directives, the Relay compiler will help you from unintentionally creating a chain with a more severe action than intended. Consider the following fragment
|
|
103
|
+
|
|
104
|
+
```graphql
|
|
105
|
+
fragment MyUser on User {
|
|
106
|
+
profile_picture @required(action: THROW) {
|
|
107
|
+
url @required(action: LOG)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
In this example we want the component to THROW if the `profile_picture` field is null but we only want to LOG an error if the `url` field is null. But recall, Relay will "bubble" nullness up to the parent field, if the `url` field is null it will then cause the `profile_picture` field to become null as well. And once that happens, the component will THROW. If you implement a pattern like this, the Relay compiler will give you an error
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
A @required field may not have an `action` less severe than that of its @required parent. This @required directive should probably have `action: LOG` so that it can match its parent
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
To fix this, either change the `profile_picture` to use `action: LOG` or change the `url` field to use `action: THROW`.
|
|
119
|
+
|
|
120
|
+
## Caveats with Connections
|
|
121
|
+
|
|
122
|
+
There are currently some limitations in using the `@required` and `@connection` directives together. When you use the `@connection` directive, Relay automatically inserts some additional fields into the connection, and those fields won't be generated with the `@required` directive. This can result in inconsistencies if you use the `@required` directive on fields in a Connection type. Consider the following example:
|
|
123
|
+
|
|
124
|
+
```graphql
|
|
125
|
+
fragment FriendsList on User @refetchable(queryName: "FriendsListQuery") {
|
|
126
|
+
friends(after: $cursor, first: $count) @connection(key: "FriendsList_friends") {
|
|
127
|
+
edges {
|
|
128
|
+
node @required(action: LOG) {
|
|
129
|
+
job @required(action: LOG) {
|
|
130
|
+
title @required(action: LOG)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Any usages of `@required` on the `node` field or any of its direct child fields will cause the Relay compiler to give you an error saying `All references to a field must have matching @required declarations.`. In order to bypass this you'll need to remove the `@required` directives on those fields.
|
|
139
|
+
|
|
140
|
+
In the above example, we'd need to remove the `@required` directives on both the `node` and `job` fields, but the usage on the `title` field would not create an error.
|
|
141
|
+
|
|
142
|
+
```graphql
|
|
143
|
+
fragment FriendsList on User @refetchable(queryName: "FriendsListQuery") {
|
|
144
|
+
friends(after: $cursor, first: $count) @connection(key: "FriendsList_friends") {
|
|
145
|
+
edges {
|
|
146
|
+
node {
|
|
147
|
+
job {
|
|
148
|
+
title @required(action: LOG)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## FAQ
|
|
157
|
+
|
|
158
|
+
### Why did @required make a non-nullable field/root nullable?
|
|
159
|
+
|
|
160
|
+
When using the `LOG` or `NONE` actions, Relay will "bubble" a missing field up to its parent field or fragment root. This means that adding `@required(action: LOG)` (for example) to a child of a non-nullable fragment root will cause the type of the fragment root to become nullable.
|
|
161
|
+
|
|
162
|
+
### What happens if you use `@required` in a plural field
|
|
163
|
+
|
|
164
|
+
If a `@required(action: LOG)` field is missing in a plural field, the _item_ in the list will be returned as null. It will _not_ cause the entire array to become null.. If you have any question about how it will behave, you can inspect the generated Flow types.
|
|
165
|
+
|
|
166
|
+
### Why are @required fields in an inline fragment still nullable?
|
|
167
|
+
|
|
168
|
+
Imagine a fragment like this:
|
|
169
|
+
|
|
170
|
+
```graphql
|
|
171
|
+
fragment MyFrag on Actor {
|
|
172
|
+
... on User {
|
|
173
|
+
name @required(action: THROW)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
It's possible that your `Actor` will not be a `User` and therefore not include a `name`. To represent that in types, we generate a Flow type that looks like this: `{name?: string}`.
|
|
179
|
+
|
|
180
|
+
If you encounter this issue, you can add a `__typename` like this:
|
|
181
|
+
|
|
182
|
+
```graphql
|
|
183
|
+
fragment MyFrag on Actor {
|
|
184
|
+
__typename
|
|
185
|
+
... on User {
|
|
186
|
+
name @required(action: THROW)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
In this situation Relay will generate a union type like: `{__typename: 'User', name: string} | {__typename: '%ignore this%}`. Now you can check the `__typename` field to narrow your object's type down to one that has a non-nullable `name`.
|
|
192
|
+
|
|
193
|
+
<FbInternalOnly>
|
|
194
|
+
|
|
195
|
+
Example diff showing the adoption of this strategy: D24370183
|
|
196
|
+
|
|
197
|
+
</FbInternalOnly>
|
|
198
|
+
|
|
199
|
+
### Why not implement this at the schema/server level?
|
|
200
|
+
|
|
201
|
+
The "requiredness" of a field is actually a product decision and not a schema question. Therefore we need to implement the handling of it at the product level. Individual components need to be able to decide for themselves how to handle a missing value.
|
|
202
|
+
|
|
203
|
+
For example, if a notification is trying to show the price for a Marketplace listing, it could probably just omit the price and still render. If payment flow for that same listing is missing the price, it should probably blow up.
|
|
204
|
+
|
|
205
|
+
Another issue is that changes to the server schema are much more difficult to ship since they affect all existing clients across all platforms.
|
|
206
|
+
|
|
207
|
+
Basically every value returned by Relay is nullable. This is intentional since we want to be able to handle field-level errors whenever possible. If we lean into KillsParentOnException we would end up wanting to make basically every field use it and our apps would be becomes more brittle since errors which used to be small, become large.
|
|
208
|
+
|
|
209
|
+
<FbInternalOnly>
|
|
210
|
+
|
|
211
|
+
_Extracted from [this comment thread](https://fb.workplace.com/groups/cometeng/permalink/937671436726844/?comment_id=937681186725869)._
|
|
212
|
+
_Further discussion in [this comment thread](https://fb.workplace.com/groups/cometeng/permalink/937671436726844/?comment_id=938335873327067)._
|
|
213
|
+
|
|
214
|
+
</FbInternalOnly>
|
|
215
|
+
|
|
216
|
+
### Can `(action: NONE)` be the default?
|
|
217
|
+
|
|
218
|
+
On one hand action: NONE makes the most sense as a default (omitted action == no action). However, we are aware that whichever value we choose as the default will be considered the default action for engineers to choose since it's the path of least resistance.
|
|
219
|
+
|
|
220
|
+
We actually believe that in most cases LOG is the most ideal choice. It gives the component a chance to gracefully recover while also giving us signal that a part of our app is rendering in a sub-optimal way.
|
|
221
|
+
|
|
222
|
+
We debated making LOG the default action for that reason, but I think that's confusing as well.
|
|
223
|
+
|
|
224
|
+
So, for now we are planning to not offer a default argument. After all, it's still much less to write out than the equivalent manual null checks. Once we see how people use it we will consider what value (if any) should be the default.
|
|
225
|
+
|
|
226
|
+
<FbInternalOnly>
|
|
227
|
+
|
|
228
|
+
### Does @required change anything about the logger project field?
|
|
229
|
+
|
|
230
|
+
When using recoverableViolation or unrecoverableViolation, the second argument is the FBLogger project name ([defined on Comet here](https://fburl.com/diffusion/rn99dl4s)):
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
recoverableViolation('My error string', 'my_logger_project');
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
When you switch to using `@required`, any `THROW` or `LOG` actions will log to the `relay-required` logger project instead ([see here in logview](https://fburl.com/logview/l40t7cjv)).
|
|
237
|
+
|
|
238
|
+
For most teams, this shouldn't be an issue; care has been taken to ensure tasks still get routed to the correct owner of the file that is using `@required`. However, if your team has any queries that utilize the logger project field, you may want to consider the implications.
|
|
239
|
+
|
|
240
|
+
</FbInternalOnly>
|