relay-runtime 20.1.0 → 21.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/experimental.js +1 -1
- package/experimental.js.flow +8 -8
- package/handlers/connection/ConnectionHandler.js.flow +5 -5
- package/handlers/connection/ConnectionInterface.js.flow +1 -1
- 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 +113 -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 +82 -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 +213 -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 +66 -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.js.flow +2 -2
- package/multi-actor-environment/ActorSpecificEnvironment.js.flow +7 -7
- package/multi-actor-environment/ActorUtils.js.flow +1 -1
- package/multi-actor-environment/MultiActorEnvironment.js.flow +12 -8
- package/multi-actor-environment/MultiActorEnvironmentTypes.js.flow +3 -3
- 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.js.flow +2 -2
- package/mutations/commitMutation.js.flow +20 -16
- 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/RelayNetworkTypes.js.flow +4 -4
- package/network/RelayObservable.js.flow +16 -14
- package/network/RelayQueryResponseCache.js.flow +3 -3
- package/network/wrapNetworkWithLogObserver.js.flow +1 -1
- package/package.json +2 -1
- package/query/GraphQLTag.js.flow +22 -10
- package/query/fetchQuery.js.flow +23 -10
- package/query/fetchQuery_DEPRECATED.js.flow +1 -1
- package/store/DataChecker.js.flow +43 -9
- package/store/NormalizationEngine.js.flow +779 -0
- package/store/OperationExecutor.js.flow +173 -70
- 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.js.flow +41 -26
- package/store/RelayModernFragmentSpecResolver.js.flow +1 -1
- package/store/RelayModernOperationDescriptor.js.flow +1 -1
- package/store/RelayModernRecord.js.flow +46 -22
- package/store/RelayModernSelector.js.flow +21 -21
- package/store/RelayModernStore.js.flow +219 -58
- package/store/RelayOperationTracker.js.flow +2 -2
- package/store/RelayOptimisticRecordSource.js.flow +2 -2
- package/store/RelayPublishQueue.js.flow +21 -12
- package/store/RelayReader.js.flow +130 -58
- package/store/RelayRecordSource.js.flow +10 -0
- package/store/RelayRecordState.js.flow +1 -1
- package/store/RelayReferenceMarker.js.flow +5 -4
- package/store/RelayResponseNormalizer.js.flow +130 -54
- package/store/RelayStoreSubscriptions.js.flow +52 -8
- package/store/RelayStoreTypes.js.flow +153 -64
- package/store/RelayStoreUtils.js.flow +15 -7
- package/store/ResolverCache.js.flow +2 -2
- package/store/ResolverFragments.js.flow +12 -12
- package/store/StoreInspector.js.flow +6 -7
- package/store/cloneRelayHandleSourceField.js.flow +1 -1
- package/store/cloneRelayScalarHandleSourceField.js.flow +1 -1
- package/store/createRelayContext.js.flow +1 -1
- package/store/createRelayLoggingContext.js.flow +4 -4
- package/store/defaultGetDataID.js.flow +2 -2
- 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.js.flow +8 -5
- package/store/observeFragmentExperimental.js.flow +49 -20
- package/store/observeQueryExperimental.js.flow +5 -5
- package/store/readInlineData.js.flow +4 -4
- package/store/waitForFragmentExperimental.js.flow +3 -3
- package/subscription/requestSubscription.js.flow +7 -7
- package/util/NormalizationNode.js.flow +34 -32
- package/util/ReaderNode.js.flow +32 -30
- package/util/RelayConcreteNode.js.flow +5 -5
- package/util/RelayError.js.flow +4 -1
- package/util/RelayFeatureFlags.js.flow +21 -1
- package/util/RelayProfiler.js.flow +1 -1
- package/util/RelayReplaySubject.js.flow +3 -3
- package/util/RelayRuntimeTypes.js.flow +11 -11
- package/util/createPayloadFor3DField.js.flow +9 -5
- package/util/deepFreeze.js.flow +2 -2
- package/util/getFragmentIdentifier.js.flow +1 -1
- package/util/getPaginationMetadata.js.flow +1 -1
- package/util/getPaginationVariables.js.flow +1 -1
- package/util/getPendingOperationsForFragment.js.flow +2 -2
- package/util/getRefetchMetadata.js.flow +6 -5
- package/util/getValueAtPath.js.flow +3 -3
- package/util/handlePotentialSnapshotErrors.js.flow +5 -5
- package/util/isEmptyObject.js.flow +1 -1
- package/util/isPromise.js.flow +2 -2
- package/util/isScalarAndEqual.js.flow +1 -1
- package/util/recycleNodesInto.js.flow +2 -2
- package/util/registerEnvironmentWithDevTools.js.flow +1 -1
- package/util/shallowFreeze.js.flow +1 -1
- package/util/stableCopy.js.flow +5 -5
- package/util/withProvidedVariables.js.flow +14 -10
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Refetchable Fragments
|
|
2
|
+
|
|
3
|
+
In this section, we'll look at how to fetch different data in response to user input.
|
|
4
|
+
|
|
5
|
+
* We'll build a **filterable friends list**.
|
|
6
|
+
* We'll see how to refetch only the necessary data, not an entire query.
|
|
7
|
+
|
|
8
|
+
* * *
|
|
9
|
+
|
|
10
|
+
Since Relay encourages you to fetch all of your data in one big query, what happens when you need to refetch some data with different variables?
|
|
11
|
+
|
|
12
|
+
For example, suppose you were building a filterable list. You would need to fetch new search results when the search input changed.
|
|
13
|
+
|
|
14
|
+
One way to approach this would be to use a separate, secondary query to fetch the list, much like we did to fetch the hovercard earlier. Then we could change the query variables and refetch the query when the input changed.
|
|
15
|
+
|
|
16
|
+
However, this isn't optimal, because it needlessly uses a second query to fetch the *initial* list, before any user input occurs. The hovercard only appeared in response to a user interaction, but if the filterable list is visible and ready to be filtered, we might as well get its initial contents as part of our big query.
|
|
17
|
+
|
|
18
|
+
On the other hand, we don't want to refetch the *entire big query* whenever the input changes. Not only would this mean retrieving a large amount of data unnecessarily, it could disrupt other parts of the UI. If certain data unrelated to the filterable list has changed on the server, it would appear to randomly change when the query was refetched. Besides, this would mean threading the user input up to the top of the React tree where the query lives, which would not scale very well.
|
|
19
|
+
|
|
20
|
+
To address these issues, Relay provides *refetchable fragments*. These are fragments that can be refetched with new variables, separately from the rest of the query that they get spread into. They allow us to change a fragment’s arguments and fetch new data for the new argument values, just as we can fetch an entire query with new query variables.
|
|
21
|
+
|
|
22
|
+
But fragments are just that, fragments — they aren’t queries and can’t be fetched without being spread into a query and read out from the query results. So how do refetchable fragments actually work? The answer is that the Relay compiler generates a new, separate query just to refetch the fragment. The data is retrieved *initially* as part of whatever larger query the fragment is spread into, but then when it’s refetched, the new synthetic query is used.
|
|
23
|
+
|
|
24
|
+
* * *
|
|
25
|
+
|
|
26
|
+
To try this out, let's add a sidebar to the page with a filterable contacts list. After all, it wouldn't feel like a properly cozy newsfeed app without the ability to contact people.
|
|
27
|
+
|
|
28
|
+
We've already prepared a `Sidebar` component, you just need to drop it into `App.tsx`:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
// change-line
|
|
32
|
+
import Sidebar from './Sidebar';
|
|
33
|
+
|
|
34
|
+
export default function App(): React.ReactElement {
|
|
35
|
+
return (
|
|
36
|
+
<RelayEnvironment>
|
|
37
|
+
<React.Suspense fallback={<LoadingSpinner />}>
|
|
38
|
+
<div className="app">
|
|
39
|
+
<Newsfeed />
|
|
40
|
+
// change-line
|
|
41
|
+
<Sidebar />
|
|
42
|
+
</div>
|
|
43
|
+
</React.Suspense>
|
|
44
|
+
</RelayEnvironment>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
You should now see a sidebar with a list of people at the top.
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
Have a look at `ContactsList.tsx` and you’ll find this fragment, which is what selects the list of contacts:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
const ContactsListFragment = graphql`
|
|
57
|
+
fragment ContactsListFragment on Viewer {
|
|
58
|
+
contacts {
|
|
59
|
+
id
|
|
60
|
+
...ContactRowFragment
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
As it happens, the `contacts` field accepts a `search` argument that filters the list. You can try it out by changing `contacts` in this fragment to `contacts(search: "S")`. If you run `npm run relay` and refresh the page, you should see only those contacts that have the letter S in them.
|
|
67
|
+
|
|
68
|
+
Our goal, then, will be to hook up a search input so that, when the input changes, we refetch *just this fragment* with a new value for that `search` argument.
|
|
69
|
+
|
|
70
|
+
:::tip
|
|
71
|
+
As an optional exercise, try combining the queries of Sidebar and Newsfeed into a single query. There is no need for Sidebar to have its own query separate from Newsfeed; in a real app they would both have fragments and the *entire screen* would have only a single query. We built it with a separate query to simplify the early examples in the tutorial.
|
|
72
|
+
:::
|
|
73
|
+
|
|
74
|
+
### Step 1 — Add a fragment argument
|
|
75
|
+
|
|
76
|
+
First we need to make this fragment accept an argument. With refetchable fragments, fragment arguments become query variables for the refetch query that Relay generates. (They also work like regular fragment arguments, so the parent query can pass in an initial value for the argument.)
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
const ContactsListFragment = graphql`
|
|
80
|
+
fragment ContactsListFragment on Viewer
|
|
81
|
+
// change
|
|
82
|
+
@argumentDefinitions(
|
|
83
|
+
search: {type: "String", defaultValue: null}
|
|
84
|
+
)
|
|
85
|
+
// end-change
|
|
86
|
+
{
|
|
87
|
+
contacts {
|
|
88
|
+
id
|
|
89
|
+
...ContactRowFragment
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Step 2 — Pass the fragment argument as a field argument
|
|
96
|
+
|
|
97
|
+
Pass the fragment argument in as an argument to the `contacts` field.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
const ContactsListFragment = graphql`
|
|
101
|
+
fragment ContactsListFragment on Viewer
|
|
102
|
+
@argumentDefinitions(
|
|
103
|
+
search: {type: "String", defaultValue: null}
|
|
104
|
+
)
|
|
105
|
+
{
|
|
106
|
+
// change-line
|
|
107
|
+
contacts(search: $search) {
|
|
108
|
+
id
|
|
109
|
+
...ContactRowFragment
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Remember, the first `search` here is the name of the argument to `contacts`, while the second `$search` is the variable created by our fragment argument.
|
|
116
|
+
|
|
117
|
+
### Step 3 — Add the @refetchable directive
|
|
118
|
+
|
|
119
|
+
Next we'll add a `@refetchable` directive. This tells Relay to generate the extra query for refetching it. You have to specify the name of the generated query — it's a good idea to base it on the name of the fragment.
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
const ContactsListFragment = graphql`
|
|
123
|
+
fragment ContactsListFragment on Viewer
|
|
124
|
+
// change-line
|
|
125
|
+
@refetchable(queryName: "ContactsListRefetchQuery")
|
|
126
|
+
@argumentDefinitions(
|
|
127
|
+
search: {type: "String", defaultValue: null}
|
|
128
|
+
)
|
|
129
|
+
{
|
|
130
|
+
// ...
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Step 4 — Add the search input
|
|
136
|
+
|
|
137
|
+
Now we need to actually hook this up to our UI. Take a look at the `ContactsList` component:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
export default function ContactsList({ viewer }: Props) {
|
|
141
|
+
const data = useFragment(ContactsListFragment, viewer);
|
|
142
|
+
return (
|
|
143
|
+
<Card dim={true}>
|
|
144
|
+
<h3>Contacts</h3>
|
|
145
|
+
{data.contacts.map(contact =>
|
|
146
|
+
<ContactRow key={contact.id} contact={contact} />
|
|
147
|
+
)}
|
|
148
|
+
</Card>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
First we need to add a search field.
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
// change-line
|
|
157
|
+
import SearchInput from './SearchInput';
|
|
158
|
+
|
|
159
|
+
// change-line
|
|
160
|
+
const {useState} = React;
|
|
161
|
+
|
|
162
|
+
function ContactsList({viewer}) {
|
|
163
|
+
const data = useFragment(ContactsListFragment, viewer);
|
|
164
|
+
// change-line
|
|
165
|
+
const [searchString, setSearchString] = useState('');
|
|
166
|
+
// change
|
|
167
|
+
const onSearchStringChanged = (value: string) => {
|
|
168
|
+
setSearchString(value);
|
|
169
|
+
};
|
|
170
|
+
// end-change
|
|
171
|
+
return (
|
|
172
|
+
<Card dim={true}>
|
|
173
|
+
<h3>Contacts</h3>
|
|
174
|
+
// change
|
|
175
|
+
<SearchInput
|
|
176
|
+
value={searchString}
|
|
177
|
+
onChange={onSearchStringChanged}
|
|
178
|
+
/>
|
|
179
|
+
// end-change
|
|
180
|
+
{data.contacts.map(contact =>
|
|
181
|
+
<ContactRow key={contact.id} contact={contact} />
|
|
182
|
+
)}
|
|
183
|
+
</Card>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Step 5 — Call useRefetchableFragment
|
|
189
|
+
|
|
190
|
+
Now to refetch the fragment when the string changes, we change `useFragment` to `useRefetchableFragment`. This hook returns a `refetch` function which will refetch the fragment with new variables which we provide as an argument.
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
// change-line
|
|
194
|
+
import {useRefetchableFragment} from 'react-relay';
|
|
195
|
+
|
|
196
|
+
function ContactsList({viewer}) {
|
|
197
|
+
// change-line
|
|
198
|
+
const [data, refetch] = useRefetchableFragment(ContactsListFragment, viewer);
|
|
199
|
+
const [searchString, setSearchString] = useState('');
|
|
200
|
+
const onSearchStringChanged = (value) => {
|
|
201
|
+
setSearchString(value);
|
|
202
|
+
// change-line
|
|
203
|
+
refetch({search: value});
|
|
204
|
+
};
|
|
205
|
+
return (
|
|
206
|
+
// ...
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
You’ll notice that Relay gives us a callback for refetching, rather than accepting the new state variables as an argument to the hook and refetching when it is re-rendered with a different value. This means that the fetch begins as soon as the event takes place, saving some time versus waiting until React finishes re-rendering — the same principle we saw before with preloaded queries. It also gives us more control, for example if we wanted to debounce the refetch.
|
|
212
|
+
|
|
213
|
+
### Step 6 — Control loading with useTransition
|
|
214
|
+
|
|
215
|
+
At this point, when the fragment is refreshed, Relay uses Suspense while the new data is loading, so the entire component is replaced with a spinner! This makes the UI fairly unusable. We would rather just keep the current data on screen until the new data is available.
|
|
216
|
+
|
|
217
|
+
The way Suspense normally works is this: When a component is missing data that it needs to render (as our component does after we refetch), it tells React to wait. When this happens, React finds the nearest Suspense component in the tree. It then replaces everything under that component with a "fallback" loading indicator.
|
|
218
|
+
|
|
219
|
+

|
|
220
|
+

|
|
221
|
+

|
|
222
|
+
|
|
223
|
+
This makes sense when initially loading a screen, but in this instance there's no reason to hide the existing UI and replace it with a spinner. While React is waiting, it can simply continue showing what's already there.
|
|
224
|
+
|
|
225
|
+
To achieve this, we can mark the refetch as a *transition*. Transitions are React state updates that do not need to be immediately responded to — React can wait until the data is available.
|
|
226
|
+
|
|
227
|
+
Transitions are marked by wrapping the state change in a call to a function provided by the `useTransition` hook. This is what the code will look like:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
// change-line
|
|
231
|
+
const {useState, useTransition} = React;
|
|
232
|
+
|
|
233
|
+
function ContactsList({viewer}) {
|
|
234
|
+
// change-line
|
|
235
|
+
const [isPending, startTransition] = useTransition();
|
|
236
|
+
const [searchString, setSearchString] = useState('');
|
|
237
|
+
const [data, refetch] = useRefetchableFragment(ContactsListFragment, viewer);
|
|
238
|
+
const onSearchStringChanged = (value) => {
|
|
239
|
+
setSearchString(value);
|
|
240
|
+
// change
|
|
241
|
+
startTransition(() => {
|
|
242
|
+
refetch({search: value});
|
|
243
|
+
});
|
|
244
|
+
// end-change
|
|
245
|
+
};
|
|
246
|
+
return (
|
|
247
|
+
<Card dim={true}>
|
|
248
|
+
<h3>Contacts</h3>
|
|
249
|
+
<SearchInput
|
|
250
|
+
value={searchString}
|
|
251
|
+
onChange={onSearchStringChanged}
|
|
252
|
+
// change-line
|
|
253
|
+
isPending={isPending}
|
|
254
|
+
/>
|
|
255
|
+
{data.contacts.map(contact =>
|
|
256
|
+
<ContactRow key={contact.id} contact={contact} />
|
|
257
|
+
)}
|
|
258
|
+
</Card>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
While React is waiting for the new data, instead of using a Suspense fallback, React re-renders the component with the `isPending` flag set to true.
|
|
264
|
+
|
|
265
|
+
We simply pass the `isPending` flag to `SearchInput` (which causes it to show a spinner) while the refetch is happening. Meanwhile, by placing `setSearchString` outside of the transition but `refetch` within it, we tell React to immediately update the search input.
|
|
266
|
+
|
|
267
|
+
We should now be able to search the contacts list with a nice user experience, showing a spinner but keeping the previous data visible while loading.
|
|
268
|
+
|
|
269
|
+

|
|
270
|
+
|
|
271
|
+
<details>
|
|
272
|
+
<summary>Deep dive: What fragments can be refetched?</summary>
|
|
273
|
+
|
|
274
|
+
To refetch fragments, Relay has to know how to generate a query that lets it refetch just the information from the fragment. That’s only possible for fragments that meet certain requirements.
|
|
275
|
+
|
|
276
|
+
You might imagine that we could, if nothing else, re-run the original query that the fragment was spread into. However, GraphQL doesn’t guarantee that the same query will return the same results at different times. For instance, imagine you had a GraphQL field that returned the top trending posts across the site:
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
query MyQuery {
|
|
280
|
+
topTrendingPosts {
|
|
281
|
+
title
|
|
282
|
+
summary
|
|
283
|
+
date
|
|
284
|
+
poster {
|
|
285
|
+
...PosterFragment
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
If you wanted to refresh just `PosterFragment` from this query, it wouldn’t work to construct a query like this:
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
query MyQuery {
|
|
295
|
+
topTrendingPosts {
|
|
296
|
+
poster {
|
|
297
|
+
...PosterFragment
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
... because the top trending post could be a different post by the time you refresh it!
|
|
304
|
+
|
|
305
|
+
Relay needs a way of identifying the specific node in the graph that the fragment ends up on, even if it can no longer be reached by the same path that the original query uses. If the node has a unique and stable ID, then we can just have a convention for querying for “the graph node with some specific ID” like so:
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
query RefetchQuery {
|
|
309
|
+
node(id: "abcdef") {
|
|
310
|
+
...PosterFragment
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
In fact, this is exactly the convention that Relay uses. It expects your server to implement a top-level field called `node` that takes an ID and gives you the graph node with that ID. (We saw `node` earlier with the hovercard example — there it was used to fetch a specific person given their ID using a secondary query.)
|
|
316
|
+
|
|
317
|
+
Not every graph node has a stable ID — some are ephemeral. To be used with `node`, your schema has to declare that its type implements an interface called `Node`:
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
type Person implements Node {
|
|
321
|
+
id: ID!
|
|
322
|
+
...
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The `Node` interface simply says it has an ID, but more importantly indicates by convention that that ID is stable and unique:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
interface Node {
|
|
330
|
+
id: ID!
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
|
|
335
|
+
|
|
336
|
+
Besides fragments on types that implement `Node`, you can also refetch fragments that are on `Viewer` (since the viewer is assumed to be stable throughout a session) and that are at the top level of a query (since there’s no field above them that could change identity).
|
|
337
|
+
|
|
338
|
+
<FbInternalOnly>
|
|
339
|
+
|
|
340
|
+
Meta only: Ents marked with <a href="https://fb.workplace.com/groups/graphql.fyi/permalink/1539541276187011/" target="_blank">GraphQLFetchable</a> can also be refetched.
|
|
341
|
+
|
|
342
|
+
</FbInternalOnly>
|
|
343
|
+
|
|
344
|
+
</details>
|
|
345
|
+
|
|
346
|
+
* * *
|
|
347
|
+
|
|
348
|
+
## Summary
|
|
349
|
+
|
|
350
|
+
Refetchable fragments let us efficiently update specific parts of the UI in response to user input, while initializing them as part of the same query that we use for the entire screen.
|
|
351
|
+
|
|
352
|
+
Relay's pagination features are built on refetchable fragments, too. We'll explore those next.
|
|
@@ -30,12 +30,12 @@ function assertInternalActorIdentifier(actorIdentifier: ActorIdentifier): void {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
module.exports = {
|
|
33
|
+
INTERNAL_ACTOR_IDENTIFIER_DO_NOT_USE,
|
|
33
34
|
assertInternalActorIdentifier,
|
|
34
35
|
getActorIdentifier(actorID: string): ActorIdentifier {
|
|
35
|
-
return
|
|
36
|
+
return actorID as ActorIdentifier;
|
|
36
37
|
},
|
|
37
38
|
getDefaultActorIdentifier(): ActorIdentifier {
|
|
38
39
|
throw new Error('Not Implemented');
|
|
39
40
|
},
|
|
40
|
-
INTERNAL_ACTOR_IDENTIFIER_DO_NOT_USE,
|
|
41
41
|
};
|
|
@@ -46,7 +46,7 @@ const RelayOperationTracker = require('../store/RelayOperationTracker');
|
|
|
46
46
|
const RelayPublishQueue = require('../store/RelayPublishQueue');
|
|
47
47
|
const registerEnvironmentWithDevTools = require('../util/registerEnvironmentWithDevTools');
|
|
48
48
|
|
|
49
|
-
export type ActorSpecificEnvironmentConfig =
|
|
49
|
+
export type ActorSpecificEnvironmentConfig = Readonly<{
|
|
50
50
|
actorIdentifier: ActorIdentifier,
|
|
51
51
|
configName: ?string,
|
|
52
52
|
defaultRenderPolicy: RenderPolicy,
|
|
@@ -56,7 +56,7 @@ export type ActorSpecificEnvironmentConfig = $ReadOnly<{
|
|
|
56
56
|
network: INetwork,
|
|
57
57
|
relayFieldLogger: RelayFieldLogger,
|
|
58
58
|
store: Store,
|
|
59
|
-
missingFieldHandlers:
|
|
59
|
+
missingFieldHandlers: ReadonlyArray<MissingFieldHandler>,
|
|
60
60
|
}>;
|
|
61
61
|
|
|
62
62
|
class ActorSpecificEnvironment implements IActorEnvironment {
|
|
@@ -69,7 +69,7 @@ class ActorSpecificEnvironment implements IActorEnvironment {
|
|
|
69
69
|
+actorIdentifier: ActorIdentifier;
|
|
70
70
|
+configName: ?string;
|
|
71
71
|
+multiActorEnvironment: IMultiActorEnvironment;
|
|
72
|
-
+options:
|
|
72
|
+
+options: unknown;
|
|
73
73
|
relayFieldLogger: RelayFieldLogger;
|
|
74
74
|
|
|
75
75
|
constructor(config: ActorSpecificEnvironmentConfig) {
|
|
@@ -101,7 +101,7 @@ class ActorSpecificEnvironment implements IActorEnvironment {
|
|
|
101
101
|
|
|
102
102
|
if (__DEV__) {
|
|
103
103
|
const {inspect} = require('../store/StoreInspector');
|
|
104
|
-
(this
|
|
104
|
+
(this as $FlowFixMe).DEBUG_inspect = (dataID: ?string) =>
|
|
105
105
|
inspect(this, dataID);
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -118,7 +118,7 @@ class ActorSpecificEnvironment implements IActorEnvironment {
|
|
|
118
118
|
return this._defaultRenderPolicy;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
applyMutation<TMutation
|
|
121
|
+
applyMutation<TMutation extends MutationParameters>(
|
|
122
122
|
optimisticConfig: OptimisticResponseConfig<TMutation>,
|
|
123
123
|
): Disposable {
|
|
124
124
|
return this.multiActorEnvironment.applyMutation(this, optimisticConfig);
|
|
@@ -202,14 +202,14 @@ class ActorSpecificEnvironment implements IActorEnvironment {
|
|
|
202
202
|
return this.multiActorEnvironment.execute(this, config);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
executeSubscription<TMutation
|
|
205
|
+
executeSubscription<TMutation extends MutationParameters>(config: {
|
|
206
206
|
operation: OperationDescriptor,
|
|
207
207
|
updater?: ?SelectorStoreUpdater<TMutation['response']>,
|
|
208
208
|
}): RelayObservable<GraphQLResponse> {
|
|
209
209
|
return this.multiActorEnvironment.executeSubscription(this, config);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
executeMutation<TMutation
|
|
212
|
+
executeMutation<TMutation extends MutationParameters>(
|
|
213
213
|
options: ExecuteMutationConfig<TMutation>,
|
|
214
214
|
): RelayObservable<GraphQLResponse> {
|
|
215
215
|
return this.multiActorEnvironment.executeMutation(this, options);
|
|
@@ -17,7 +17,7 @@ import type {ActorIdentifier} from './ActorIdentifier';
|
|
|
17
17
|
|
|
18
18
|
const {getActorIdentifier} = require('./ActorIdentifier');
|
|
19
19
|
|
|
20
|
-
function getActorIdentifierFromPayload(payload:
|
|
20
|
+
function getActorIdentifierFromPayload(payload: unknown): ?ActorIdentifier {
|
|
21
21
|
if (
|
|
22
22
|
payload != null &&
|
|
23
23
|
typeof payload === 'object' &&
|
|
@@ -55,7 +55,7 @@ const RelayModernStore = require('../store/RelayModernStore');
|
|
|
55
55
|
const RelayRecordSource = require('../store/RelayRecordSource');
|
|
56
56
|
const ActorSpecificEnvironment = require('./ActorSpecificEnvironment');
|
|
57
57
|
|
|
58
|
-
export type MultiActorEnvironmentConfig =
|
|
58
|
+
export type MultiActorEnvironmentConfig = Readonly<{
|
|
59
59
|
createConfigNameForActor?: ?(actorIdentifier: ActorIdentifier) => string,
|
|
60
60
|
createNetworkForActor: (actorIdentifier: ActorIdentifier) => INetwork,
|
|
61
61
|
createStoreForActor?: ?(actorIdentifier: ActorIdentifier) => Store,
|
|
@@ -64,13 +64,14 @@ export type MultiActorEnvironmentConfig = $ReadOnly<{
|
|
|
64
64
|
handlerProvider?: HandlerProvider,
|
|
65
65
|
isServer?: ?boolean,
|
|
66
66
|
logFn?: ?LogFunction,
|
|
67
|
-
missingFieldHandlers?:
|
|
67
|
+
missingFieldHandlers?: ?ReadonlyArray<MissingFieldHandler>,
|
|
68
68
|
normalizeResponse?: NormalizeResponseFunction,
|
|
69
69
|
operationLoader?: ?OperationLoader,
|
|
70
70
|
relayFieldLogger?: ?RelayFieldLogger,
|
|
71
71
|
scheduler?: ?TaskScheduler,
|
|
72
72
|
shouldProcessClientComponents?: ?boolean,
|
|
73
73
|
treatMissingFieldsAsNull?: boolean,
|
|
74
|
+
deferDeduplicatedFields?: boolean,
|
|
74
75
|
}>;
|
|
75
76
|
|
|
76
77
|
class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
@@ -83,7 +84,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
|
83
84
|
+_handlerProvider: HandlerProvider;
|
|
84
85
|
+_isServer: boolean;
|
|
85
86
|
+_logFn: LogFunction;
|
|
86
|
-
+_missingFieldHandlers:
|
|
87
|
+
+_missingFieldHandlers: ReadonlyArray<MissingFieldHandler>;
|
|
87
88
|
+_normalizeResponse: NormalizeResponseFunction;
|
|
88
89
|
+_operationExecutions: Map<string, ActiveState>;
|
|
89
90
|
+_operationLoader: ?OperationLoader;
|
|
@@ -91,6 +92,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
|
91
92
|
+_scheduler: ?TaskScheduler;
|
|
92
93
|
+_shouldProcessClientComponents: ?boolean;
|
|
93
94
|
+_treatMissingFieldsAsNull: boolean;
|
|
95
|
+
+_deferDeduplicatedFields: boolean;
|
|
94
96
|
|
|
95
97
|
constructor(config: MultiActorEnvironmentConfig) {
|
|
96
98
|
this._actorEnvironments = new Map();
|
|
@@ -106,6 +108,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
|
106
108
|
this._relayFieldLogger = config.relayFieldLogger ?? defaultRelayFieldLogger;
|
|
107
109
|
this._shouldProcessClientComponents = config.shouldProcessClientComponents;
|
|
108
110
|
this._treatMissingFieldsAsNull = config.treatMissingFieldsAsNull ?? false;
|
|
111
|
+
this._deferDeduplicatedFields = config.deferDeduplicatedFields ?? false;
|
|
109
112
|
this._isServer = config.isServer ?? false;
|
|
110
113
|
this._missingFieldHandlers = config.missingFieldHandlers ?? [];
|
|
111
114
|
this._createStoreForActor = config.createStoreForActor;
|
|
@@ -175,7 +178,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
|
175
178
|
_checkSelectorAndHandleMissingFields(
|
|
176
179
|
actorEnvironment: IActorEnvironment,
|
|
177
180
|
operation: OperationDescriptor,
|
|
178
|
-
handlers:
|
|
181
|
+
handlers: ReadonlyArray<MissingFieldHandler>,
|
|
179
182
|
): OperationAvailability {
|
|
180
183
|
const targets: Map<ActorIdentifier, MutableRecordSource> = new Map([
|
|
181
184
|
[actorEnvironment.actorIdentifier, RelayRecordSource.create()],
|
|
@@ -267,7 +270,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
|
267
270
|
});
|
|
268
271
|
}
|
|
269
272
|
|
|
270
|
-
applyMutation<TMutation
|
|
273
|
+
applyMutation<TMutation extends MutationParameters>(
|
|
271
274
|
actorEnvironment: IActorEnvironment,
|
|
272
275
|
optimisticConfig: OptimisticResponseConfig<TMutation>,
|
|
273
276
|
): Disposable {
|
|
@@ -341,7 +344,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
|
341
344
|
});
|
|
342
345
|
}
|
|
343
346
|
|
|
344
|
-
executeSubscription<TMutation
|
|
347
|
+
executeSubscription<TMutation extends MutationParameters>(
|
|
345
348
|
actorEnvironment: IActorEnvironment,
|
|
346
349
|
{
|
|
347
350
|
operation,
|
|
@@ -368,7 +371,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
|
368
371
|
});
|
|
369
372
|
}
|
|
370
373
|
|
|
371
|
-
executeMutation<TMutation
|
|
374
|
+
executeMutation<TMutation extends MutationParameters>(
|
|
372
375
|
actorEnvironment: IActorEnvironment,
|
|
373
376
|
{
|
|
374
377
|
operation,
|
|
@@ -432,7 +435,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
|
432
435
|
return this._isServer;
|
|
433
436
|
}
|
|
434
437
|
|
|
435
|
-
_execute<TMutation
|
|
438
|
+
_execute<TMutation extends MutationParameters>(
|
|
436
439
|
actorEnvironment: IActorEnvironment,
|
|
437
440
|
{
|
|
438
441
|
createSource,
|
|
@@ -471,6 +474,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
|
|
|
471
474
|
return this.forActor(actorIdentifier).getStore();
|
|
472
475
|
},
|
|
473
476
|
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
|
|
477
|
+
deferDeduplicatedFields: this._deferDeduplicatedFields,
|
|
474
478
|
updater,
|
|
475
479
|
log: this._logFn,
|
|
476
480
|
normalizeResponse: this._normalizeResponse,
|
|
@@ -140,7 +140,7 @@ export interface IMultiActorEnvironment {
|
|
|
140
140
|
* Apply an optimistic mutation response and/or updater. The mutation can be
|
|
141
141
|
* reverted by calling `dispose()` on the returned value.
|
|
142
142
|
*/
|
|
143
|
-
applyMutation<TMutation
|
|
143
|
+
applyMutation<TMutation extends MutationParameters>(
|
|
144
144
|
actorEnvironment: IActorEnvironment,
|
|
145
145
|
optimisticConfig: OptimisticResponseConfig<TMutation>,
|
|
146
146
|
): Disposable;
|
|
@@ -203,7 +203,7 @@ export interface IMultiActorEnvironment {
|
|
|
203
203
|
* Note: Observables are lazy, so calling this method will do nothing until
|
|
204
204
|
* the result is subscribed to: environment.executeSubscription({...}).subscribe({...}).
|
|
205
205
|
*/
|
|
206
|
-
executeSubscription<TMutation
|
|
206
|
+
executeSubscription<TMutation extends MutationParameters>(
|
|
207
207
|
actorEnvironment: IActorEnvironment,
|
|
208
208
|
config: {
|
|
209
209
|
operation: OperationDescriptor,
|
|
@@ -221,7 +221,7 @@ export interface IMultiActorEnvironment {
|
|
|
221
221
|
* the result is subscribed to:
|
|
222
222
|
* environment.executeMutation({...}).subscribe({...}).
|
|
223
223
|
*/
|
|
224
|
-
executeMutation<TMutation
|
|
224
|
+
executeMutation<TMutation extends MutationParameters>(
|
|
225
225
|
actorEnvironment: IActorEnvironment,
|
|
226
226
|
config: ExecuteMutationConfig<TMutation>,
|
|
227
227
|
): RelayObservable<GraphQLResponse>;
|
|
@@ -27,13 +27,13 @@ const MutationTypes = Object.freeze({
|
|
|
27
27
|
RANGE_DELETE: 'RANGE_DELETE',
|
|
28
28
|
NODE_DELETE: 'NODE_DELETE',
|
|
29
29
|
});
|
|
30
|
-
export type MutationType =
|
|
30
|
+
export type MutationType = Values<typeof MutationTypes>;
|
|
31
31
|
|
|
32
32
|
const RangeOperations = Object.freeze({
|
|
33
33
|
APPEND: 'append',
|
|
34
34
|
PREPEND: 'prepend',
|
|
35
35
|
});
|
|
36
|
-
export type RangeOperation =
|
|
36
|
+
export type RangeOperation = Values<typeof RangeOperations>;
|
|
37
37
|
|
|
38
38
|
type RangeBehaviorsFunction = (connectionArgs: {
|
|
39
39
|
[name: string]: $FlowFixMe,
|
|
@@ -82,7 +82,7 @@ export type DeclarativeMutationConfig =
|
|
|
82
82
|
| RangeDeleteConfig
|
|
83
83
|
| NodeDeleteConfig;
|
|
84
84
|
|
|
85
|
-
function convert<TMutation
|
|
85
|
+
function convert<TMutation extends MutationParameters>(
|
|
86
86
|
configs: Array<DeclarativeMutationConfig>,
|
|
87
87
|
request: ConcreteRequest,
|
|
88
88
|
optimisticUpdater?: ?SelectorStoreUpdater<TMutation['response']>,
|
|
@@ -145,13 +145,13 @@ function convert<TMutation: MutationParameters>(
|
|
|
145
145
|
function nodeDelete(
|
|
146
146
|
config: NodeDeleteConfig,
|
|
147
147
|
request: ConcreteRequest,
|
|
148
|
-
): ?SelectorStoreUpdater<
|
|
148
|
+
): ?SelectorStoreUpdater<unknown> {
|
|
149
149
|
const {deletedIDFieldName} = config;
|
|
150
150
|
const rootField = getRootField(request);
|
|
151
151
|
if (!rootField) {
|
|
152
152
|
return null;
|
|
153
153
|
}
|
|
154
|
-
return (store: RecordSourceSelectorProxy, data: ?
|
|
154
|
+
return (store: RecordSourceSelectorProxy, data: ?unknown) => {
|
|
155
155
|
const payload = store.getRootField(rootField);
|
|
156
156
|
if (!payload) {
|
|
157
157
|
return;
|
|
@@ -169,7 +169,7 @@ function nodeDelete(
|
|
|
169
169
|
function rangeAdd(
|
|
170
170
|
config: RangeAddConfig,
|
|
171
171
|
request: ConcreteRequest,
|
|
172
|
-
): ?SelectorStoreUpdater<
|
|
172
|
+
): ?SelectorStoreUpdater<unknown> {
|
|
173
173
|
const {parentID, connectionInfo, edgeName} = config;
|
|
174
174
|
if (!parentID) {
|
|
175
175
|
warning(
|
|
@@ -183,7 +183,7 @@ function rangeAdd(
|
|
|
183
183
|
if (!connectionInfo || !rootField) {
|
|
184
184
|
return null;
|
|
185
185
|
}
|
|
186
|
-
return (store: RecordSourceSelectorProxy, data: ?
|
|
186
|
+
return (store: RecordSourceSelectorProxy, data: ?unknown) => {
|
|
187
187
|
const parent = store.get(parentID);
|
|
188
188
|
if (!parent) {
|
|
189
189
|
return;
|
|
@@ -237,7 +237,7 @@ function rangeAdd(
|
|
|
237
237
|
function rangeDelete(
|
|
238
238
|
config: RangeDeleteConfig,
|
|
239
239
|
request: ConcreteRequest,
|
|
240
|
-
): ?SelectorStoreUpdater<
|
|
240
|
+
): ?SelectorStoreUpdater<unknown> {
|
|
241
241
|
const {parentID, connectionKeys, pathToConnection, deletedIDFieldName} =
|
|
242
242
|
config;
|
|
243
243
|
if (!parentID) {
|
|
@@ -252,7 +252,7 @@ function rangeDelete(
|
|
|
252
252
|
if (!rootField) {
|
|
253
253
|
return null;
|
|
254
254
|
}
|
|
255
|
-
return (store: RecordSourceSelectorProxy, data: ?
|
|
255
|
+
return (store: RecordSourceSelectorProxy, data: ?unknown) => {
|
|
256
256
|
if (!data) {
|
|
257
257
|
return;
|
|
258
258
|
}
|