react-relay 18.0.0 → 18.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. package/ReactRelayContext.js +1 -1
  2. package/ReactRelayFragmentContainer.js.flow +2 -5
  3. package/buildReactRelayContainer.js.flow +1 -0
  4. package/hooks.js +1 -1
  5. package/index.js +1 -1
  6. package/index.js.flow +3 -0
  7. package/legacy.js +1 -1
  8. package/lib/index.js +2 -0
  9. package/lib/relay-hooks/getConnectionState.js +47 -0
  10. package/lib/relay-hooks/legacy/FragmentResource.js +3 -8
  11. package/lib/relay-hooks/loadQuery.js +5 -14
  12. package/lib/relay-hooks/readFragmentInternal.js +2 -4
  13. package/lib/relay-hooks/useFragmentInternal.js +1 -1
  14. package/lib/relay-hooks/useFragmentInternal_CURRENT.js +3 -10
  15. package/lib/relay-hooks/useFragmentInternal_EXPERIMENTAL.js +6 -28
  16. package/lib/relay-hooks/useLoadMoreFunction.js +10 -43
  17. package/lib/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js +130 -0
  18. package/lib/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js +227 -0
  19. package/lib/relay-hooks/useQueryLoader.js +8 -0
  20. package/lib/relay-hooks/useQueryLoader_EXPERIMENTAL.js +120 -0
  21. package/package.json +2 -2
  22. package/react-relay-hooks.js +2 -2
  23. package/react-relay-hooks.min.js +2 -2
  24. package/react-relay-legacy.js +1 -1
  25. package/react-relay-legacy.min.js +1 -1
  26. package/react-relay.js +2 -2
  27. package/react-relay.min.js +2 -2
  28. package/relay-hooks/EntryPointTypes.flow.js.flow +2 -2
  29. package/relay-hooks/MatchContainer.js.flow +1 -1
  30. package/relay-hooks/getConnectionState.js.flow +97 -0
  31. package/relay-hooks/legacy/FragmentResource.js.flow +4 -16
  32. package/relay-hooks/loadQuery.js.flow +30 -38
  33. package/relay-hooks/readFragmentInternal.js.flow +1 -10
  34. package/relay-hooks/useFragmentInternal.js.flow +1 -1
  35. package/relay-hooks/useFragmentInternal_CURRENT.js.flow +7 -22
  36. package/relay-hooks/useFragmentInternal_EXPERIMENTAL.js.flow +8 -56
  37. package/relay-hooks/useLoadMoreFunction.js.flow +14 -80
  38. package/relay-hooks/useLoadMoreFunction_EXPERIMENTAL.js.flow +280 -0
  39. package/relay-hooks/usePrefetchableForwardPaginationFragment_EXPERIMENTAL.js.flow +433 -0
  40. package/relay-hooks/useQueryLoader.js.flow +27 -3
  41. package/relay-hooks/useQueryLoader_EXPERIMENTAL.js.flow +253 -0
@@ -25,9 +25,10 @@ import type {
25
25
 
26
26
  const {loadQuery} = require('./loadQuery');
27
27
  const useIsMountedRef = require('./useIsMountedRef');
28
+ const useQueryLoader_EXPERIMENTAL = require('./useQueryLoader_EXPERIMENTAL');
28
29
  const useRelayEnvironment = require('./useRelayEnvironment');
29
30
  const {useCallback, useEffect, useRef, useState} = require('react');
30
- const {getRequest} = require('relay-runtime');
31
+ const {RelayFeatureFlags, getRequest} = require('relay-runtime');
31
32
 
32
33
  export type LoaderFn<TQuery: OperationType> = (
33
34
  variables: TQuery['variables'],
@@ -42,7 +43,7 @@ export type UseQueryLoaderLoadQueryOptions = $ReadOnly<{
42
43
  // NullQueryReference needs to implement referential equality,
43
44
  // so that multiple NullQueryReferences can be in the same set
44
45
  // (corresponding to multiple calls to disposeQuery).
45
- type NullQueryReference = {
46
+ export type NullQueryReference = {
46
47
  kind: 'NullQueryReference',
47
48
  };
48
49
  const initialNullQueryReferenceState = {kind: 'NullQueryReference'};
@@ -68,7 +69,7 @@ function requestIsLiveQuery<
68
69
  return request.params.metadata.live !== undefined;
69
70
  }
70
71
 
71
- type UseQueryLoaderHookReturnType<
72
+ export type UseQueryLoaderHookReturnType<
72
73
  TVariables: Variables,
73
74
  TData,
74
75
  TRawResponse: ?{...} = void,
@@ -115,6 +116,29 @@ hook useQueryLoader<TVariables: Variables, TData, TRawResponse: ?{...} = void>(
115
116
  variables: TVariables,
116
117
  rawResponse?: $NonMaybeType<TRawResponse>,
117
118
  }>,
119
+ ): UseQueryLoaderHookReturnType<TVariables, TData> {
120
+ if (RelayFeatureFlags.ENABLE_ACTIVITY_COMPATIBILITY) {
121
+ // $FlowFixMe[react-rule-hook] - the condition is static
122
+ return useQueryLoader_EXPERIMENTAL(
123
+ preloadableRequest,
124
+ initialQueryReference,
125
+ );
126
+ }
127
+ // $FlowFixMe[react-rule-hook] - the condition is static
128
+ return useQueryLoader_CURRENT(preloadableRequest, initialQueryReference);
129
+ }
130
+
131
+ hook useQueryLoader_CURRENT<
132
+ TVariables: Variables,
133
+ TData,
134
+ TRawResponse: ?{...} = void,
135
+ >(
136
+ preloadableRequest: Query<TVariables, TData, TRawResponse>,
137
+ initialQueryReference?: ?PreloadedQuery<{
138
+ response: TData,
139
+ variables: TVariables,
140
+ rawResponse?: $NonMaybeType<TRawResponse>,
141
+ }>,
118
142
  ): UseQueryLoaderHookReturnType<TVariables, TData> {
119
143
  type QueryType = {
120
144
  response: TData,
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ * @oncall relay
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ import type {
15
+ PreloadableConcreteRequest,
16
+ PreloadedQuery,
17
+ } from './EntryPointTypes.flow';
18
+ import type {
19
+ NullQueryReference,
20
+ UseQueryLoaderHookReturnType,
21
+ UseQueryLoaderLoadQueryOptions,
22
+ } from './useQueryLoader';
23
+ import type {OperationType, Query, Variables} from 'relay-runtime';
24
+
25
+ const {loadQuery} = require('./loadQuery');
26
+ const useIsMountedRef = require('./useIsMountedRef');
27
+ const useRelayEnvironment = require('./useRelayEnvironment');
28
+ const {
29
+ useCallback,
30
+ useEffect,
31
+ // $FlowFixMe[prop-missing] Remove once callers are migrated to official API
32
+ useInsertionEffect,
33
+ useRef,
34
+ useState,
35
+ } = require('react');
36
+ const {getRequest} = require('relay-runtime');
37
+
38
+ const initialNullQueryReferenceState = {kind: 'NullQueryReference'};
39
+
40
+ function requestIsLiveQuery<
41
+ TVariables: Variables,
42
+ TData,
43
+ TRawResponse: ?{...} = void,
44
+ TQuery: OperationType = {
45
+ response: TData,
46
+ variables: TVariables,
47
+ rawResponse?: $NonMaybeType<TRawResponse>,
48
+ },
49
+ >(
50
+ preloadableRequest:
51
+ | Query<TVariables, TData, TRawResponse>
52
+ | PreloadableConcreteRequest<TQuery>,
53
+ ): boolean {
54
+ if (preloadableRequest.kind === 'PreloadableConcreteRequest') {
55
+ return preloadableRequest.params.metadata.live !== undefined;
56
+ }
57
+ const request = getRequest(preloadableRequest);
58
+ return request.params.metadata.live !== undefined;
59
+ }
60
+
61
+ const CLEANUP_TIMEOUT = 1000 * 60 * 5; // 5 minutes;
62
+
63
+ hook useQueryLoader_EXPERIMENTAL<
64
+ TVariables: Variables,
65
+ TData,
66
+ TRawResponse: ?{...} = void,
67
+ >(
68
+ preloadableRequest: Query<TVariables, TData, TRawResponse>,
69
+ initialQueryReference?: ?PreloadedQuery<{
70
+ response: TData,
71
+ variables: TVariables,
72
+ rawResponse?: $NonMaybeType<TRawResponse>,
73
+ }>,
74
+ ): UseQueryLoaderHookReturnType<TVariables, TData> {
75
+ type QueryType = {
76
+ response: TData,
77
+ variables: TVariables,
78
+ rawResponse?: $NonMaybeType<TRawResponse>,
79
+ };
80
+
81
+ /**
82
+ * We want to always call `queryReference.dispose()` for every call to
83
+ * `setQueryReference(loadQuery(...))` so that no leaks of data in Relay stores
84
+ * will occur.
85
+ *
86
+ * However, a call to `setState(newState)` is not always followed by a commit where
87
+ * this value is reflected in the state. Thus, we cannot reliably clean up each
88
+ * ref with `useEffect(() => () => queryReference.dispose(), [queryReference])`.
89
+ *
90
+ * Instead, we keep track of each call to `loadQuery` in a ref.
91
+ * Relying on the fact that if a state change commits, no state changes that were
92
+ * initiated prior to the currently committing state change will ever subsequently
93
+ * commit, we can safely dispose of all preloaded query references
94
+ * associated with state changes initiated prior to the currently committing state
95
+ * change.
96
+ *
97
+ * Finally, when the hook unmounts, we also dispose of all remaining uncommitted
98
+ * query references.
99
+ */
100
+
101
+ const initialQueryReferenceInternal =
102
+ initialQueryReference ?? initialNullQueryReferenceState;
103
+
104
+ const environment = useRelayEnvironment();
105
+
106
+ const isMountedRef = useIsMountedRef();
107
+ const undisposedQueryReferencesRef = useRef<Set<
108
+ PreloadedQuery<QueryType> | NullQueryReference,
109
+ > | null>(null);
110
+ if (undisposedQueryReferencesRef.current == null) {
111
+ undisposedQueryReferencesRef.current = new Set([
112
+ initialQueryReferenceInternal,
113
+ ]);
114
+ }
115
+
116
+ const [queryReference, setQueryReference] = useState<
117
+ PreloadedQuery<QueryType> | NullQueryReference,
118
+ >(() => initialQueryReferenceInternal);
119
+
120
+ const [previousInitialQueryReference, setPreviousInitialQueryReference] =
121
+ useState<PreloadedQuery<QueryType> | NullQueryReference>(
122
+ () => initialQueryReferenceInternal,
123
+ );
124
+
125
+ if (initialQueryReferenceInternal !== previousInitialQueryReference) {
126
+ // Rendering the query reference makes it "managed" by this hook, so
127
+ // we start keeping track of it so we can dispose it when it is no longer
128
+ // necessary here
129
+ // TODO(T78446637): Handle disposal of managed query references in
130
+ // components that were never mounted after rendering
131
+ // $FlowFixMe[react-rule-unsafe-ref]
132
+ undisposedQueryReferencesRef.current?.add(initialQueryReferenceInternal);
133
+ setPreviousInitialQueryReference(initialQueryReferenceInternal);
134
+ setQueryReference(initialQueryReferenceInternal);
135
+ }
136
+
137
+ const disposeQuery = useCallback(() => {
138
+ if (isMountedRef.current) {
139
+ undisposedQueryReferencesRef.current?.add(initialNullQueryReferenceState);
140
+ setQueryReference(initialNullQueryReferenceState);
141
+ }
142
+ }, [isMountedRef]);
143
+
144
+ const queryLoaderCallback = useCallback(
145
+ (variables: TVariables, options?: ?UseQueryLoaderLoadQueryOptions) => {
146
+ if (!isMountedRef.current) {
147
+ return;
148
+ }
149
+ const mergedOptions: ?UseQueryLoaderLoadQueryOptions =
150
+ options != null && options.hasOwnProperty('__environment')
151
+ ? {
152
+ fetchPolicy: options.fetchPolicy,
153
+ networkCacheConfig: options.networkCacheConfig,
154
+ __nameForWarning: options.__nameForWarning,
155
+ }
156
+ : options;
157
+ const updatedQueryReference = loadQuery(
158
+ options?.__environment ?? environment,
159
+ preloadableRequest,
160
+ variables,
161
+ (mergedOptions: $FlowFixMe),
162
+ );
163
+ undisposedQueryReferencesRef.current?.add(updatedQueryReference);
164
+ setQueryReference(updatedQueryReference);
165
+ },
166
+ [environment, preloadableRequest, setQueryReference, isMountedRef],
167
+ );
168
+
169
+ const disposeAllRemainingQueryReferences = useCallback(
170
+ function disposeAllRemainingQueryReferences(
171
+ preloadableRequest: Query<TVariables, TData, TRawResponse>,
172
+ currentQueryReference:
173
+ | PreloadedQuery<QueryType>
174
+ | NullQueryReference
175
+ | null,
176
+ ) {
177
+ const undisposedQueryReferences =
178
+ undisposedQueryReferencesRef.current ?? new Set();
179
+ // undisposedQueryReferences.current is never reassigned
180
+ // eslint-disable-next-line react-hooks/exhaustive-deps
181
+ for (const undisposedQueryReference of undisposedQueryReferences) {
182
+ if (undisposedQueryReference === currentQueryReference) {
183
+ continue;
184
+ }
185
+ if (undisposedQueryReference.kind !== 'NullQueryReference') {
186
+ if (requestIsLiveQuery(preloadableRequest)) {
187
+ undisposedQueryReference.dispose &&
188
+ undisposedQueryReference.dispose();
189
+ } else {
190
+ undisposedQueryReference.releaseQuery &&
191
+ undisposedQueryReference.releaseQuery();
192
+ }
193
+ }
194
+ }
195
+ },
196
+ [],
197
+ );
198
+
199
+ const cleanupTimerRef = useRef<?TimeoutID>(null);
200
+ useEffect(() => {
201
+ // When a new queryReference is committed, we iterate over all
202
+ // query references in undisposedQueryReferences and dispose all of
203
+ // the refs that aren't the currently committed one. This ensures
204
+ // that we don't leave any dangling query references for the
205
+ // case that loadQuery is called multiple times before commit; when
206
+ // this happens, multiple state updates will be scheduled, but only one
207
+ // will commit, meaning that we need to keep track of and dispose any
208
+ // query references that don't end up committing.
209
+ // - We are relying on the fact that sets iterate in insertion order, and we
210
+ // can remove items from a set as we iterate over it (i.e. no iterator
211
+ // invalidation issues.) Thus, it is safe to loop through
212
+ // undisposedQueryReferences until we find queryReference, and
213
+ // remove and dispose all previous references.
214
+ // - We are guaranteed to find queryReference in the set, because if a
215
+ // state update results in a commit, no state updates initiated prior to that
216
+ // one will be committed, and we are disposing and removing references
217
+ // associated with updates that were scheduled prior to the currently
218
+ // committing state change. (A useEffect callback is called during the commit
219
+ // phase.)
220
+ disposeAllRemainingQueryReferences(preloadableRequest, queryReference);
221
+ if (cleanupTimerRef.current != null) {
222
+ clearTimeout(cleanupTimerRef.current);
223
+ cleanupTimerRef.current = null;
224
+ }
225
+ return () => {
226
+ cleanupTimerRef.current = setTimeout(() => {
227
+ disposeAllRemainingQueryReferences(preloadableRequest, null);
228
+ }, CLEANUP_TIMEOUT);
229
+ };
230
+ }, [preloadableRequest, queryReference]);
231
+
232
+ // $FlowFixMe[not-a-function]
233
+ useInsertionEffect(() => {
234
+ // We use an insertion effect to ensure that we cleanup the final query
235
+ // reference when the component truly unmounts. Note that a regular
236
+ // useEffect may detach/reattach due to <Activity> multiple times, and
237
+ // we don't want to free queries that may be used when the component reveals
238
+ // again.
239
+ return () => {
240
+ cleanupTimerRef.current && clearTimeout(cleanupTimerRef.current);
241
+ cleanupTimerRef.current = null;
242
+ disposeAllRemainingQueryReferences(preloadableRequest, null);
243
+ };
244
+ }, [preloadableRequest]);
245
+
246
+ return [
247
+ queryReference.kind === 'NullQueryReference' ? null : queryReference,
248
+ queryLoaderCallback,
249
+ disposeQuery,
250
+ ];
251
+ }
252
+
253
+ module.exports = useQueryLoader_EXPERIMENTAL;