Skip to content

Commit f8d9575

Browse files
jonreading81claude
andcommitted
Drop redundant isRequestActive gate on in-flight correlation
`_operationExecutions` flips to `'inactive'` while `_state === 'loading_final'` (with no pending module payloads) — before the network stream completes and `_inFlightOperationCompletions` is cleared in `cancel()`. Under incremental delivery, fragments reading during that "final-but-not-complete" window fall through to partial-data reads instead of suspending on the still-valid completion promise, defeating this correlation. Gate solely on the completion map entry's presence (via `getPromiseForInFlightOperation`) — populated in the Executor constructor and cleared atomically when `_complete` fires — which IS the correct in-flight signal for this branch. Adds a test in useFragment-WithOperationTrackerSuspense-test.js exercising the mid-stream window where isRequestActive returns false while the completion promise is still valid; the fragment must suspend on that promise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 26de1b0 commit f8d9575

2 files changed

Lines changed: 57 additions & 2 deletions

File tree

packages/react-relay/relay-hooks/__tests__/useFragment-WithOperationTrackerSuspense-test.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,4 +545,60 @@ describe('useFragment with Operation Tracker and Suspense behavior', () => {
545545

546546
expect(renderer?.container.textContent).toBe('Alice');
547547
});
548+
549+
it('should throw promise via in-flight correlation even when isRequestActive returns false', async () => {
550+
// Covers the mid-stream window where isRequestActive can return false
551+
// while the operation's completion promise is still in flight (e.g.
552+
// `_state === 'loading_final'` before `_complete` fires). The fragment
553+
// must still suspend on that promise instead of reading partial data.
554+
environment.execute({operation: nodeOperation}).subscribe({});
555+
556+
const realIsRequestActive =
557+
environment.isRequestActive.bind(environment);
558+
const isRequestActiveSpy = jest
559+
.spyOn(environment, 'isRequestActive')
560+
.mockImplementation(id => {
561+
if (id === nodeOperation.request.identifier) {
562+
return false;
563+
}
564+
return realIsRequestActive(id);
565+
});
566+
567+
expect(
568+
environment.getPromiseForInFlightOperation(
569+
nodeOperation.request.identifier,
570+
),
571+
).not.toBeNull();
572+
expect(
573+
environment.isRequestActive(nodeOperation.request.identifier),
574+
).toBe(false);
575+
576+
const fragmentRef = {
577+
__id: 'user-id-1',
578+
__fragments: {
579+
useFragmentWithOperationTrackerSuspenseTestFragment: {},
580+
},
581+
__fragmentOwner: nodeOperation.request,
582+
};
583+
584+
const renderer = await render({userRef: fragmentRef});
585+
expect(renderer?.container.textContent).toBe('Singular Fallback');
586+
587+
isRequestActiveSpy.mockRestore();
588+
589+
await act(() => {
590+
environment.mock.nextValue(nodeOperation, {
591+
data: {
592+
node: {
593+
__typename: 'User',
594+
id: 'user-id-1',
595+
name: 'Alice',
596+
},
597+
},
598+
});
599+
environment.mock.complete(nodeOperation.request.node);
600+
});
601+
602+
expect(renderer?.container.textContent).toBe('Alice');
603+
});
548604
});

packages/relay-runtime/util/getPendingOperationsForFragment.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ function getPendingOperationsForFragment(
4141

4242
if (
4343
promise == null &&
44-
RelayFeatureFlags.ENABLE_IN_FLIGHT_OPERATION_CORRELATION &&
45-
environment.isRequestActive(fragmentOwner.identifier)
44+
RelayFeatureFlags.ENABLE_IN_FLIGHT_OPERATION_CORRELATION
4645
) {
4746
const inFlightPromise = environment.getPromiseForInFlightOperation(
4847
fragmentOwner.identifier,

0 commit comments

Comments
 (0)