Only suspend on initial mount
@@ -26,7 +26,7 @@ import type {MissingClientEdgeRequestInfo} from 'relay-runtime/store/RelayStoreT
const {getQueryResourceForEnvironment} = require('../QueryResource');
const useRelayEnvironment = require('../useRelayEnvironment');
const invariant = require('invariant');
-const {useDebugValue, useEffect, useMemo, useState} = require('react');
+const {useDebugValue, useEffect, useMemo, useRef, useState} = require('react');
const {
__internal: {fetchQuery: fetchQueryInternal},
areEqualSelectors,
@@ -52,6 +52,9 @@ type FragmentState = $ReadOnly<
type StateUpdaterFunction<T> = ((T) => T) => void;
+class NotMounted {}
+const NOT_MOUNTED = new NotMounted();
+
function isMissingData(state: FragmentState): boolean {
if (state.kind === 'bailout') {
return false;
@@ -391,6 +394,18 @@ function useFragmentInternal_REACT_CACHE(
subscribedState = newState;
}
+ // The purpose of this is to detect whether we have ever committed, because we
+ // don't suspend on store updates, only when the component either is first trying
+ // to mount or when the our selector changes. The selector change in particular is
+ // how we suspend for pagination and refetech. Also, fragment selector can be null,
+ // so we use NOT_MOUNTED as a special value to distinguish from all fragment selectors.
+ const committedFragmentSelectorRef = useRef<NotMounted | ?ReaderSelector>(
+ NOT_MOUNTED,
+ );
+ useEffect(() => {
+ committedFragmentSelectorRef.current = fragmentSelector;
+ }, [fragmentSelector]);
+
// Handle the queries for any missing client edges; this may suspend.
// FIXME handle client edges in parallel.
if (fragmentNode.metadata?.hasClientEdges === true) {
@@ -439,7 +454,10 @@ function useFragmentInternal_REACT_CACHE(
if (isMissingData(state)) {
// Suspend if an active operation bears on this fragment, either the
- // fragment's owner or some other mutation etc. that could affect it:
+ // fragment's owner or some other mutation etc. that could affect it.
+ // We only suspend when the component is first trying to mount or changing
+ // selectors, not if data becomes missing later:
+ if (committedFragmentSelectorRef.current !== fragmentSelector) {
invariant(fragmentSelector != null, 'refinement, see invariants above');
const fragmentOwner =
fragmentSelector.kind === 'PluralReaderSelector'
@@ -453,6 +471,7 @@ function useFragmentInternal_REACT_CACHE(
if (pendingOperationsResult) {
throw pendingOperationsResult.promise;
}
+ }
// Report required fields only if we're not suspending, since that means
// they're missing even though we are out of options for possibly fetching them:
handlePotentialSnapshotErrorsForState(environment, state);
