react-relay 13.2.0 → 14.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. package/ReactRelayContext.js +1 -1
  2. package/ReactRelayFragmentContainer.js.flow +7 -4
  3. package/ReactRelayPaginationContainer.js.flow +13 -8
  4. package/ReactRelayQueryFetcher.js.flow +1 -0
  5. package/ReactRelayQueryRenderer.js.flow +6 -2
  6. package/ReactRelayRefetchContainer.js.flow +10 -3
  7. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer.graphql.js.flow +2 -2
  8. package/__flowtests__/__generated__/ReactRelayFragmentContainerFlowtest_viewer2.graphql.js.flow +2 -2
  9. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtestQuery.graphql.js.flow +3 -3
  10. package/__flowtests__/__generated__/ReactRelayPaginationContainerFlowtest_viewer.graphql.js.flow +3 -3
  11. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtestQuery.graphql.js.flow +3 -3
  12. package/__flowtests__/__generated__/ReactRelayRefetchContainerFlowtest_viewer.graphql.js.flow +3 -3
  13. package/__flowtests__/__generated__/RelayModernFlowtest_badref.graphql.js.flow +2 -2
  14. package/__flowtests__/__generated__/RelayModernFlowtest_notref.graphql.js.flow +2 -2
  15. package/__flowtests__/__generated__/RelayModernFlowtest_user.graphql.js.flow +2 -2
  16. package/__flowtests__/__generated__/RelayModernFlowtest_users.graphql.js.flow +2 -2
  17. package/buildReactRelayContainer.js.flow +2 -2
  18. package/hooks.js +1 -1
  19. package/index.js +1 -1
  20. package/legacy.js +1 -1
  21. package/lib/ReactRelayQueryFetcher.js +1 -0
  22. package/lib/ReactRelayQueryRenderer.js +0 -1
  23. package/lib/readContext.js +2 -1
  24. package/lib/relay-hooks/FragmentResource.js +52 -10
  25. package/lib/relay-hooks/HooksImplementation.js +29 -0
  26. package/lib/relay-hooks/MatchContainer.js +1 -0
  27. package/lib/relay-hooks/QueryResource.js +2 -1
  28. package/lib/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js +203 -56
  29. package/lib/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js +254 -109
  30. package/lib/relay-hooks/react-cache/useFragment_REACT_CACHE.js +51 -0
  31. package/lib/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js +13 -2
  32. package/lib/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js +125 -0
  33. package/lib/relay-hooks/useFragment.js +15 -1
  34. package/lib/relay-hooks/useLazyLoadQuery.js +18 -2
  35. package/lib/relay-hooks/useMutation.js +4 -5
  36. package/lib/relay-hooks/usePreloadedQuery.js +18 -2
  37. package/package.json +2 -2
  38. package/react-relay-hooks.js +2 -2
  39. package/react-relay-hooks.min.js +2 -2
  40. package/react-relay-legacy.js +2 -2
  41. package/react-relay-legacy.min.js +2 -2
  42. package/react-relay.js +2 -2
  43. package/react-relay.min.js +2 -2
  44. package/readContext.js.flow +1 -0
  45. package/relay-hooks/FragmentResource.js.flow +55 -9
  46. package/relay-hooks/HooksImplementation.js.flow +45 -0
  47. package/relay-hooks/MatchContainer.js.flow +8 -1
  48. package/relay-hooks/QueryResource.js.flow +4 -2
  49. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_user.graphql.js.flow +2 -2
  50. package/relay-hooks/__flowtests__/__generated__/useFragmentFlowtest_users.graphql.js.flow +2 -2
  51. package/relay-hooks/loadQuery.js.flow +2 -1
  52. package/relay-hooks/react-cache/getQueryResultOrFetchQuery_REACT_CACHE.js.flow +245 -64
  53. package/relay-hooks/react-cache/useFragmentInternal_REACT_CACHE.js.flow +242 -99
  54. package/relay-hooks/react-cache/useFragment_REACT_CACHE.js.flow +74 -0
  55. package/relay-hooks/react-cache/useLazyLoadQuery_REACT_CACHE.js.flow +10 -4
  56. package/relay-hooks/react-cache/usePreloadedQuery_REACT_CACHE.js.flow +153 -0
  57. package/relay-hooks/useFragment.js.flow +17 -10
  58. package/relay-hooks/useLazyLoadQuery.js.flow +38 -3
  59. package/relay-hooks/useMutation.js.flow +3 -3
  60. package/relay-hooks/usePreloadedQuery.js.flow +30 -2
  61. package/relay-hooks/useRefetchableFragmentNode.js.flow +26 -11
  62. package/relay-hooks/useSubscription.js.flow +14 -8
@@ -41,6 +41,8 @@ var _require2 = require('relay-runtime'),
41
41
  handlePotentialSnapshotErrors = _require2.handlePotentialSnapshotErrors,
42
42
  recycleNodesInto = _require2.recycleNodesInto;
43
43
 
44
+ var warning = require("fbjs/lib/warning");
45
+
44
46
  function isMissingData(state) {
45
47
  if (state.kind === 'bailout') {
46
48
  return false;
@@ -119,16 +121,22 @@ function handlePotentialSnapshotErrorsForState(environment, state) {
119
121
  }
120
122
  }
121
123
  }
124
+ /**
125
+ * Check for updates to the store that occurred concurrently with rendering the given `state` value,
126
+ * returning a new (updated) state if there were updates or null if there were no changes.
127
+ */
122
128
 
123
- function handleMissedUpdates(environment, state, setState) {
129
+
130
+ function handleMissedUpdates(environment, state) {
124
131
  if (state.kind === 'bailout') {
125
- return;
126
- }
132
+ return null;
133
+ } // FIXME this is invalid if we've just switched environments.
134
+
127
135
 
128
136
  var currentEpoch = environment.getStore().getEpoch();
129
137
 
130
138
  if (currentEpoch === state.epoch) {
131
- return;
139
+ return null;
132
140
  } // The store has updated since we rendered (without us being subscribed yet),
133
141
  // so check for any updates to the data we're rendering:
134
142
 
@@ -136,16 +144,24 @@ function handleMissedUpdates(environment, state, setState) {
136
144
  if (state.kind === 'singular') {
137
145
  var currentSnapshot = environment.lookup(state.snapshot.selector);
138
146
  var updatedData = recycleNodesInto(state.snapshot.data, currentSnapshot.data);
139
-
140
- if (updatedData !== state.snapshot.data) {
141
- setState({
142
- kind: 'singular',
143
- snapshot: currentSnapshot,
144
- epoch: currentEpoch
145
- });
146
- }
147
+ var updatedCurrentSnapshot = {
148
+ data: updatedData,
149
+ isMissingData: currentSnapshot.isMissingData,
150
+ missingClientEdges: currentSnapshot.missingClientEdges,
151
+ missingLiveResolverFields: currentSnapshot.missingLiveResolverFields,
152
+ seenRecords: currentSnapshot.seenRecords,
153
+ selector: currentSnapshot.selector,
154
+ missingRequiredFields: currentSnapshot.missingRequiredFields,
155
+ relayResolverErrors: currentSnapshot.relayResolverErrors
156
+ };
157
+ return [updatedData !== state.snapshot.data, {
158
+ kind: 'singular',
159
+ snapshot: updatedCurrentSnapshot,
160
+ epoch: currentEpoch
161
+ }];
147
162
  } else {
148
- var updates = null;
163
+ var didMissUpdates = false;
164
+ var currentSnapshots = [];
149
165
 
150
166
  for (var index = 0; index < state.snapshots.length; index++) {
151
167
  var snapshot = state.snapshots[index];
@@ -154,34 +170,30 @@ function handleMissedUpdates(environment, state, setState) {
154
170
 
155
171
  var _updatedData = recycleNodesInto(snapshot.data, _currentSnapshot.data);
156
172
 
173
+ var _updatedCurrentSnapshot = {
174
+ data: _updatedData,
175
+ isMissingData: _currentSnapshot.isMissingData,
176
+ missingClientEdges: _currentSnapshot.missingClientEdges,
177
+ missingLiveResolverFields: _currentSnapshot.missingLiveResolverFields,
178
+ seenRecords: _currentSnapshot.seenRecords,
179
+ selector: _currentSnapshot.selector,
180
+ missingRequiredFields: _currentSnapshot.missingRequiredFields,
181
+ relayResolverErrors: _currentSnapshot.relayResolverErrors
182
+ };
183
+
157
184
  if (_updatedData !== snapshot.data) {
158
- updates = updates === null ? new Array(state.snapshots.length) : updates;
159
- updates[index] = snapshot;
185
+ didMissUpdates = true;
160
186
  }
161
- }
162
-
163
- if (updates !== null) {
164
- var theUpdates = updates; // preserve flow refinement.
165
-
166
- setState(function (existing) {
167
- !(existing.kind === 'plural') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Cannot go from singular to plural or from bailout to plural.') : invariant(false) : void 0;
168
- var updated = (0, _toConsumableArray2["default"])(existing.snapshots);
169
187
 
170
- for (var _index = 0; _index < theUpdates.length; _index++) {
171
- var updatedSnapshot = theUpdates[_index];
172
-
173
- if (updatedSnapshot) {
174
- updated[_index] = updatedSnapshot;
175
- }
176
- }
177
-
178
- return {
179
- kind: 'plural',
180
- snapshots: updated,
181
- epoch: currentEpoch
182
- };
183
- });
188
+ currentSnapshots.push(_updatedCurrentSnapshot);
184
189
  }
190
+
191
+ !(currentSnapshots.length === state.snapshots.length) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected same number of snapshots') : invariant(false) : void 0;
192
+ return [didMissUpdates, {
193
+ kind: 'plural',
194
+ snapshots: currentSnapshots,
195
+ epoch: currentEpoch
196
+ }];
185
197
  }
186
198
  }
187
199
 
@@ -196,7 +208,13 @@ function handleMissingClientEdge(environment, parentFragmentNode, parentFragment
196
208
  // according to the component mount/suspense cycle; getQueryResultOrFetchQuery
197
209
  // already handles this by itself.
198
210
 
199
- getQueryResultOrFetchQuery(environment, queryOperationDescriptor, queryOptions === null || queryOptions === void 0 ? void 0 : queryOptions.fetchPolicy);
211
+ var _getQueryResultOrFetc = getQueryResultOrFetchQuery(environment, queryOperationDescriptor, {
212
+ fetchPolicy: queryOptions === null || queryOptions === void 0 ? void 0 : queryOptions.fetchPolicy
213
+ }),
214
+ _ = _getQueryResultOrFetc[0],
215
+ effect = _getQueryResultOrFetc[1];
216
+
217
+ return effect;
200
218
  }
201
219
 
202
220
  function subscribeToSnapshot(environment, state, setState) {
@@ -204,10 +222,12 @@ function subscribeToSnapshot(environment, state, setState) {
204
222
  return function () {};
205
223
  } else if (state.kind === 'singular') {
206
224
  var disposable = environment.subscribe(state.snapshot, function (latestSnapshot) {
207
- setState({
208
- kind: 'singular',
209
- snapshot: latestSnapshot,
210
- epoch: environment.getStore().getEpoch()
225
+ setState(function (_) {
226
+ return {
227
+ kind: 'singular',
228
+ snapshot: latestSnapshot,
229
+ epoch: environment.getStore().getEpoch()
230
+ };
211
231
  });
212
232
  });
213
233
  return function () {
@@ -246,7 +266,7 @@ function subscribeToSnapshot(environment, state, setState) {
246
266
  }
247
267
  }
248
268
 
249
- function getFragmentState(environment, fragmentSelector) {
269
+ function getFragmentState(environment, fragmentSelector, isPlural) {
250
270
  if (fragmentSelector == null) {
251
271
  return {
252
272
  kind: 'bailout'
@@ -270,66 +290,148 @@ function getFragmentState(environment, fragmentSelector) {
270
290
 
271
291
 
272
292
  function useFragmentInternal_REACT_CACHE(fragmentNode, fragmentRef, hookDisplayName, queryOptions, fragmentKey) {
273
- var _fragmentNode$metadat;
293
+ var _fragmentNode$metadat, _fragmentNode$metadat2;
274
294
 
275
- var fragmentSelector = getSelector(fragmentNode, fragmentRef);
295
+ var fragmentSelector = useMemo(function () {
296
+ return getSelector(fragmentNode, fragmentRef);
297
+ }, [fragmentNode, fragmentRef]);
298
+ var isPlural = (fragmentNode === null || fragmentNode === void 0 ? void 0 : (_fragmentNode$metadat = fragmentNode.metadata) === null || _fragmentNode$metadat === void 0 ? void 0 : _fragmentNode$metadat.plural) === true;
276
299
 
277
- if ((fragmentNode === null || fragmentNode === void 0 ? void 0 : (_fragmentNode$metadat = fragmentNode.metadata) === null || _fragmentNode$metadat === void 0 ? void 0 : _fragmentNode$metadat.plural) === true) {
278
- !Array.isArray(fragmentRef) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected fragment pointer%s for fragment `%s` to be ' + 'an array, instead got `%s`. Remove `@relay(plural: true)` ' + 'from fragment `%s` to allow the prop to be an object.', fragmentKey != null ? " for key `".concat(fragmentKey, "`") : '', fragmentNode.name, typeof fragmentRef, fragmentNode.name) : invariant(false) : void 0;
300
+ if (isPlural) {
301
+ !(fragmentRef == null || Array.isArray(fragmentRef)) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected fragment pointer%s for fragment `%s` to be ' + 'an array, instead got `%s`. Remove `@relay(plural: true)` ' + 'from fragment `%s` to allow the prop to be an object.', fragmentKey != null ? " for key `".concat(fragmentKey, "`") : '', fragmentNode.name, typeof fragmentRef, fragmentNode.name) : invariant(false) : void 0;
279
302
  } else {
280
303
  !!Array.isArray(fragmentRef) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected fragment pointer%s for fragment `%s` not to be ' + 'an array, instead got `%s`. Add `@relay(plural: true)` ' + 'to fragment `%s` to allow the prop to be an array.', fragmentKey != null ? " for key `".concat(fragmentKey, "`") : '', fragmentNode.name, typeof fragmentRef, fragmentNode.name) : invariant(false) : void 0;
281
304
  }
282
305
 
283
- !(fragmentRef == null || fragmentSelector != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected to receive an object where `...%s` was spread, ' + 'but the fragment reference was not found`. This is most ' + 'likely the result of:\n' + "- Forgetting to spread `%s` in `%s`'s parent's fragment.\n" + '- Conditionally fetching `%s` but unconditionally passing %s prop ' + 'to `%s`. If the parent fragment only fetches the fragment conditionally ' + '- with e.g. `@include`, `@skip`, or inside a `... on SomeType { }` ' + 'spread - then the fragment reference will not exist. ' + 'In this case, pass `null` if the conditions for evaluating the ' + 'fragment are not met (e.g. if the `@include(if)` value is false.)', fragmentNode.name, fragmentNode.name, hookDisplayName, fragmentNode.name, fragmentKey == null ? 'a fragment reference' : "the `".concat(fragmentKey, "`"), hookDisplayName) : invariant(false) : void 0;
306
+ !(fragmentRef == null || isPlural && Array.isArray(fragmentRef) && fragmentRef.length === 0 || fragmentSelector != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Relay: Expected to receive an object where `...%s` was spread, ' + 'but the fragment reference was not found`. This is most ' + 'likely the result of:\n' + "- Forgetting to spread `%s` in `%s`'s parent's fragment.\n" + '- Conditionally fetching `%s` but unconditionally passing %s prop ' + 'to `%s`. If the parent fragment only fetches the fragment conditionally ' + '- with e.g. `@include`, `@skip`, or inside a `... on SomeType { }` ' + 'spread - then the fragment reference will not exist. ' + 'In this case, pass `null` if the conditions for evaluating the ' + 'fragment are not met (e.g. if the `@include(if)` value is false.)', fragmentNode.name, fragmentNode.name, hookDisplayName, fragmentNode.name, fragmentKey == null ? 'a fragment reference' : "the `".concat(fragmentKey, "`"), hookDisplayName) : invariant(false) : void 0;
284
307
  var environment = useRelayEnvironment();
285
308
 
286
309
  var _useState = useState(function () {
287
- return getFragmentState(environment, fragmentSelector);
310
+ return getFragmentState(environment, fragmentSelector, isPlural);
288
311
  }),
289
312
  rawState = _useState[0],
290
- setState = _useState[1];
313
+ setState = _useState[1]; // On second look this separate rawState may not be needed at all, it can just be
314
+ // put into getFragmentState. Exception: can we properly handle the case where the
315
+ // fragmentRef goes from non-null to null?
316
+
317
+
318
+ var stateFromRawState = function stateFromRawState(state) {
319
+ if (fragmentRef == null) {
320
+ return {
321
+ kind: 'bailout'
322
+ };
323
+ } else if (state.kind === 'plural' && state.snapshots.length === 0) {
324
+ return {
325
+ kind: 'bailout'
326
+ };
327
+ } else {
328
+ return state;
329
+ }
330
+ };
291
331
 
292
- var _useState2 = useState(fragmentSelector),
293
- previousFragmentSelector = _useState2[0],
294
- setPreviousFragmentSelector = _useState2[1];
332
+ var state = stateFromRawState(rawState); // This copy of the state we only update when something requires us to
333
+ // unsubscribe and re-subscribe, namely a changed environment or
334
+ // fragment selector.
295
335
 
296
- if (!areEqualSelectors(fragmentSelector, previousFragmentSelector)) {
297
- setPreviousFragmentSelector(fragmentSelector);
298
- setState(getFragmentState(environment, fragmentSelector));
299
- }
336
+ var _useState2 = useState(state),
337
+ rawSubscribedState = _useState2[0],
338
+ setSubscribedState = _useState2[1]; // FIXME since this is used as an effect dependency, it needs to be memoized.
300
339
 
301
- var state;
302
340
 
303
- if (fragmentRef == null) {
304
- state = {
305
- kind: 'bailout'
306
- };
307
- } else if (rawState.kind === 'plural' && rawState.snapshots.length === 0) {
308
- state = {
309
- kind: 'bailout'
310
- };
311
- } else {
312
- state = rawState;
341
+ var subscribedState = stateFromRawState(rawSubscribedState);
342
+
343
+ var _useState3 = useState(fragmentSelector),
344
+ previousFragmentSelector = _useState3[0],
345
+ setPreviousFragmentSelector = _useState3[1];
346
+
347
+ var _useState4 = useState(environment),
348
+ previousEnvironment = _useState4[0],
349
+ setPreviousEnvironment = _useState4[1];
350
+
351
+ if (!areEqualSelectors(fragmentSelector, previousFragmentSelector) || environment !== previousEnvironment) {
352
+ // Enqueue setState to record the new selector and state
353
+ setPreviousFragmentSelector(fragmentSelector);
354
+ setPreviousEnvironment(environment);
355
+ var newState = stateFromRawState(getFragmentState(environment, fragmentSelector, isPlural));
356
+ setState(newState);
357
+ setSubscribedState(newState); // This causes us to form a new subscription
358
+ // But render with the latest state w/o waiting for the setState. Otherwise
359
+ // the component would render the wrong information temporarily (including
360
+ // possibly incorrectly triggering some warnings below).
361
+
362
+ state = newState;
363
+ subscribedState = newState;
313
364
  } // Handle the queries for any missing client edges; this may suspend.
314
365
  // FIXME handle client edges in parallel.
315
366
 
316
367
 
317
- var missingClientEdges = getMissingClientEdges(state);
368
+ if (((_fragmentNode$metadat2 = fragmentNode.metadata) === null || _fragmentNode$metadat2 === void 0 ? void 0 : _fragmentNode$metadat2.hasClientEdges) === true) {
369
+ // The fragment is validated to be static (in useFragment) and hasClientEdges is
370
+ // a static (constant) property of the fragment. In practice, this effect will
371
+ // always or never run for a given invocation of this hook.
372
+ // eslint-disable-next-line react-hooks/rules-of-hooks
373
+ var effects = useMemo(function () {
374
+ var missingClientEdges = getMissingClientEdges(state); // eslint-disable-next-line no-shadow
318
375
 
319
- if (missingClientEdges === null || missingClientEdges === void 0 ? void 0 : missingClientEdges.length) {
320
- var _iterator5 = (0, _createForOfIteratorHelper2["default"])(missingClientEdges),
321
- _step5;
376
+ var effects;
322
377
 
323
- try {
324
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
325
- var edge = _step5.value;
326
- handleMissingClientEdge(environment, fragmentNode, fragmentRef, edge, queryOptions);
378
+ if (missingClientEdges === null || missingClientEdges === void 0 ? void 0 : missingClientEdges.length) {
379
+ effects = [];
380
+
381
+ var _iterator5 = (0, _createForOfIteratorHelper2["default"])(missingClientEdges),
382
+ _step5;
383
+
384
+ try {
385
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
386
+ var edge = _step5.value;
387
+ effects.push(handleMissingClientEdge(environment, fragmentNode, fragmentRef, edge, queryOptions));
388
+ }
389
+ } catch (err) {
390
+ _iterator5.e(err);
391
+ } finally {
392
+ _iterator5.f();
393
+ }
327
394
  }
328
- } catch (err) {
329
- _iterator5.e(err);
330
- } finally {
331
- _iterator5.f();
332
- }
395
+
396
+ return effects;
397
+ }, [state, environment, fragmentNode, fragmentRef, queryOptions]); // See above note
398
+ // eslint-disable-next-line react-hooks/rules-of-hooks
399
+
400
+ useEffect(function () {
401
+ if (effects === null || effects === void 0 ? void 0 : effects.length) {
402
+ var cleanups = [];
403
+
404
+ var _iterator6 = (0, _createForOfIteratorHelper2["default"])(effects),
405
+ _step6;
406
+
407
+ try {
408
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
409
+ var effect = _step6.value;
410
+ cleanups.push(effect());
411
+ }
412
+ } catch (err) {
413
+ _iterator6.e(err);
414
+ } finally {
415
+ _iterator6.f();
416
+ }
417
+
418
+ return function () {
419
+ var _iterator7 = (0, _createForOfIteratorHelper2["default"])(cleanups),
420
+ _step7;
421
+
422
+ try {
423
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
424
+ var cleanup = _step7.value;
425
+ cleanup();
426
+ }
427
+ } catch (err) {
428
+ _iterator7.e(err);
429
+ } finally {
430
+ _iterator7.f();
431
+ }
432
+ };
433
+ }
434
+ }, [effects]);
333
435
  }
334
436
 
335
437
  if (isMissingData(state)) {
@@ -346,36 +448,83 @@ function useFragmentInternal_REACT_CACHE(fragmentNode, fragmentRef, hookDisplayN
346
448
 
347
449
 
348
450
  handlePotentialSnapshotErrorsForState(environment, state);
349
- } // Subscriptions:
350
-
351
-
352
- var isMountedRef = useRef(false);
353
- var isListeningForUpdatesRef = useRef(true);
354
-
355
- function enableStoreUpdates() {
356
- isListeningForUpdatesRef.current = true;
357
- handleMissedUpdates(environment, state, setState);
358
- }
359
-
360
- function disableStoreUpdates() {
361
- isListeningForUpdatesRef.current = false;
362
451
  }
363
452
 
364
453
  useEffect(function () {
365
- var wasAlreadySubscribed = isMountedRef.current;
366
- isMountedRef.current = true;
454
+ // Check for updates since the state was rendered
455
+ var currentState = subscribedState;
456
+ var updates = handleMissedUpdates(environment, subscribedState);
367
457
 
368
- if (!wasAlreadySubscribed) {
369
- handleMissedUpdates(environment, state, setState);
458
+ if (updates !== null) {
459
+ var didMissUpdates = updates[0],
460
+ updatedState = updates[1]; // TODO: didMissUpdates only checks for changes to snapshot data, but it's possible
461
+ // that other snapshot properties may have changed that should also trigger a re-render,
462
+ // such as changed missing resolver fields, missing client edges, etc.
463
+ // A potential alternative is for handleMissedUpdates() to recycle the entire state
464
+ // value, and return the new (recycled) state only if there was some change. In that
465
+ // case the code would always setState if something in the snapshot changed, in addition
466
+ // to using the latest snapshot to subscribe.
467
+
468
+ if (didMissUpdates) {
469
+ setState(updatedState);
470
+ }
471
+
472
+ currentState = updatedState;
370
473
  }
371
474
 
372
- return subscribeToSnapshot(environment, state, setState);
373
- }, [environment, state]);
374
- var data = useMemo(function () {
375
- return state.kind === 'bailout' ? {} : state.kind === 'singular' ? state.snapshot.data : state.snapshots.map(function (s) {
376
- return s.data;
475
+ return subscribeToSnapshot(environment, currentState, function (updater) {
476
+ setState(function (latestState) {
477
+ var _latestState$snapshot, _currentState$snapsho;
478
+
479
+ if (((_latestState$snapshot = latestState.snapshot) === null || _latestState$snapshot === void 0 ? void 0 : _latestState$snapshot.selector) !== ((_currentState$snapsho = currentState.snapshot) === null || _currentState$snapsho === void 0 ? void 0 : _currentState$snapsho.selector)) {
480
+ // Ignore updates to the subscription if it's for a previous fragment selector
481
+ // than the latest one to be rendered. This can happen if the store is updated
482
+ // after we re-render with a new fragmentRef prop but before the effect fires
483
+ // in which we unsubscribe to the old one and subscribe to the new one.
484
+ // (NB: it's safe to compare the selectors by reference because the selector
485
+ // is recycled into new snapshots.)
486
+ return latestState;
487
+ } else {
488
+ return updater(latestState);
489
+ }
490
+ });
377
491
  });
378
- }, [state]);
492
+ }, [environment, subscribedState]);
493
+ var data;
494
+
495
+ if (isPlural) {
496
+ // Plural fragments require allocating an array of the snasphot data values,
497
+ // which has to be memoized to avoid triggering downstream re-renders.
498
+ //
499
+ // Note that isPlural is a constant property of the fragment and does not change
500
+ // for a particular useFragment invocation site
501
+ // eslint-disable-next-line react-hooks/rules-of-hooks
502
+ data = useMemo(function () {
503
+ if (state.kind === 'bailout') {
504
+ return [];
505
+ } else {
506
+ !(state.kind === 'plural') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected state to be plural because fragment is plural') : invariant(false) : void 0;
507
+ return state.snapshots.map(function (s) {
508
+ return s.data;
509
+ });
510
+ }
511
+ }, [state]);
512
+ } else if (state.kind === 'bailout') {
513
+ // This case doesn't allocate a new object so it doesn't have to be memoized
514
+ data = null;
515
+ } else {
516
+ // This case doesn't allocate a new object so it doesn't have to be memoized
517
+ !(state.kind === 'singular') ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected state to be singular because fragment is singular') : invariant(false) : void 0;
518
+ data = state.snapshot.data;
519
+ }
520
+
521
+ if (process.env.NODE_ENV !== "production") {
522
+ if (fragmentRef != null && (data === undefined || Array.isArray(data) && data.length > 0 && data.every(function (d) {
523
+ return d === undefined;
524
+ }))) {
525
+ process.env.NODE_ENV !== "production" ? warning(false, 'Relay: Expected to have been able to read non-null data for ' + 'fragment `%s` declared in ' + '`%s`, since fragment reference was non-null. ' + "Make sure that that `%s`'s parent isn't " + 'holding on to and/or passing a fragment reference for data that ' + 'has been deleted.', fragmentNode.name, hookDisplayName, hookDisplayName) : void 0;
526
+ }
527
+ }
379
528
 
380
529
  if (process.env.NODE_ENV !== "production") {
381
530
  // eslint-disable-next-line react-hooks/rules-of-hooks
@@ -385,11 +534,7 @@ function useFragmentInternal_REACT_CACHE(fragmentNode, fragmentRef, hookDisplayN
385
534
  });
386
535
  }
387
536
 
388
- return {
389
- data: data,
390
- disableStoreUpdates: disableStoreUpdates,
391
- enableStoreUpdates: enableStoreUpdates
392
- };
537
+ return data;
393
538
  }
394
539
 
395
540
  module.exports = useFragmentInternal_REACT_CACHE;
@@ -0,0 +1,51 @@
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
+ * @emails oncall+relay
8
+ *
9
+ * @format
10
+ */
11
+ // flowlint ambiguous-object-type:error
12
+ 'use strict';
13
+
14
+ var _require = require('../loadQuery'),
15
+ useTrackLoadQueryInRender = _require.useTrackLoadQueryInRender;
16
+
17
+ var useStaticFragmentNodeWarning = require('../useStaticFragmentNodeWarning');
18
+
19
+ var useFragmentInternal = require('./useFragmentInternal_REACT_CACHE');
20
+
21
+ var _require2 = require('react'),
22
+ useDebugValue = _require2.useDebugValue;
23
+
24
+ var _require3 = require('relay-runtime'),
25
+ getFragment = _require3.getFragment;
26
+
27
+ function useFragment(fragment, key) {
28
+ // We need to use this hook in order to be able to track if
29
+ // loadQuery was called during render
30
+ useTrackLoadQueryInRender();
31
+ var fragmentNode = getFragment(fragment);
32
+
33
+ if (process.env.NODE_ENV !== "production") {
34
+ // eslint-disable-next-line react-hooks/rules-of-hooks
35
+ useStaticFragmentNodeWarning(fragmentNode, 'first argument of useFragment()');
36
+ }
37
+
38
+ var data = useFragmentInternal(fragmentNode, key, 'useFragment()');
39
+
40
+ if (process.env.NODE_ENV !== "production") {
41
+ // eslint-disable-next-line react-hooks/rules-of-hooks
42
+ useDebugValue({
43
+ fragment: fragmentNode.name,
44
+ data: data
45
+ });
46
+ }
47
+
48
+ return data;
49
+ }
50
+
51
+ module.exports = useFragment;
@@ -22,6 +22,9 @@ var getQueryResultOrFetchQuery = require('./getQueryResultOrFetchQuery_REACT_CAC
22
22
 
23
23
  var useFragmentInternal = require('./useFragmentInternal_REACT_CACHE');
24
24
 
25
+ var _require2 = require('react'),
26
+ useEffect = _require2.useEffect;
27
+
25
28
  function useLazyLoadQuery_REACT_CACHE(gqlQuery, variables, options) {
26
29
  var _options$networkCache;
27
30
 
@@ -31,7 +34,15 @@ function useLazyLoadQuery_REACT_CACHE(gqlQuery, variables, options) {
31
34
  force: true
32
35
  }); // Get the query going if needed -- this may suspend.
33
36
 
34
- var queryResult = getQueryResultOrFetchQuery(environment, queryOperationDescriptor, options === null || options === void 0 ? void 0 : options.fetchPolicy); // Read the query's root fragment -- this may suspend.
37
+ var _getQueryResultOrFetc = getQueryResultOrFetchQuery(environment, queryOperationDescriptor, {
38
+ fetchPolicy: options === null || options === void 0 ? void 0 : options.fetchPolicy,
39
+ renderPolicy: options === null || options === void 0 ? void 0 : options.UNSTABLE_renderPolicy,
40
+ fetchKey: options === null || options === void 0 ? void 0 : options.fetchKey
41
+ }),
42
+ queryResult = _getQueryResultOrFetc[0],
43
+ effect = _getQueryResultOrFetc[1];
44
+
45
+ useEffect(effect); // Read the query's root fragment -- this may suspend.
35
46
 
36
47
  var fragmentNode = queryResult.fragmentNode,
37
48
  fragmentRef = queryResult.fragmentRef; // $FlowExpectedError[incompatible-return] Is this a fixable incompatible-return?
@@ -39,7 +50,7 @@ function useLazyLoadQuery_REACT_CACHE(gqlQuery, variables, options) {
39
50
  return useFragmentInternal(fragmentNode, fragmentRef, 'useLazyLoadQuery()', {
40
51
  fetchPolicy: options === null || options === void 0 ? void 0 : options.fetchPolicy,
41
52
  networkCacheConfig: options === null || options === void 0 ? void 0 : options.networkCacheConfig
42
- }).data;
53
+ });
43
54
  }
44
55
 
45
56
  module.exports = useLazyLoadQuery_REACT_CACHE;