Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions __tests__/ReactRelayQueryRenderer-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const loadingState = {
retry: expect.any(Function),
isLoading: true,
};

/*
function expectToBeRendered(render, readyState) {
const calls = render.mock.calls;
// Ensure useEffect is called before other timers
Expand All @@ -71,7 +71,14 @@ function expectToBeRendered(render, readyState) {
expect(calls[1][0]).toEqual({ ...readyState, isLoading: false });
return { pass: true };
}

*/
function expectToBeRendered(render, readyState) {
const calls = render.mock.calls;
// Ensure useEffect is called before other timers
expect(calls.length).toBe(1);
expect(calls[0][0]).toEqual({ ...readyState, isLoading: false });
return { pass: true };
}
const QueryRendererHook = (props) => {
const {
render,
Expand Down Expand Up @@ -119,6 +126,9 @@ describe('ReactRelayQueryRenderer', () => {
name: 'Zuck',
},
},
extensions: {
is_final: true,
},
};

const responseErrors = {
Expand All @@ -129,6 +139,9 @@ describe('ReactRelayQueryRenderer', () => {
name: 'Zuck',
},
},
extensions: {
is_final: true,
},
errors: [
{
message: 'error',
Expand Down Expand Up @@ -358,11 +371,10 @@ describe('ReactRelayQueryRenderer', () => {
unstable_concurrentUpdatesByDefault: true,
});
});

// Flush some of the changes, but don't commit
(Scheduler as any).unstable_flushNumberOfYields(2);


expect((Scheduler as any).unstable_clearYields()).toEqual(['A', 'B']);
expect(renderer.toJSON()).toEqual(null);
expect().loadingRendered();
Expand All @@ -373,7 +385,6 @@ describe('ReactRelayQueryRenderer', () => {
renderer.update(<Example />);
});


expect(environment.execute.mock.calls.length).toBe(1);
expect().loadingRendered();
});
Expand Down Expand Up @@ -647,6 +658,9 @@ describe('ReactRelayQueryRenderer', () => {
name: 'Other',
},
},
extensions: {
is_final: true,
},
};
const thirdRequest = pendingRequests[2];
const thirdOwner = thirdRequest.request.operation;
Expand All @@ -658,15 +672,18 @@ describe('ReactRelayQueryRenderer', () => {
name: 'Third',
},
},
extensions: {
is_final: true,
},
};

// Resolve the latest request first, and the earlier request last
// The query renderer should render the data from the latest
// request
thirdRequest.resolve(thirdResponse);
secondRequest.resolve(secondResponse);
expect(render.mock.calls.length).toEqual(4);
const lastRender = render.mock.calls[3][0];
expect(render.mock.calls.length).toEqual(3);
const lastRender = render.mock.calls[2][0];
expect(lastRender).toEqual({
error: null,
data: {
Expand Down Expand Up @@ -1250,7 +1267,7 @@ describe('ReactRelayQueryRenderer', () => {
});

it('refetch the query if `retry`', () => {
expect.assertions(7);
expect.assertions(6);
render.mockClear();
const error = new Error('network fails');
environment.mock.reject(TestQuery, error);
Expand Down Expand Up @@ -1313,7 +1330,7 @@ describe('ReactRelayQueryRenderer', () => {
}
}
const renderer = createHooks(<Example />);
expect.assertions(7);
expect.assertions(5);
mockA.mockClear();
mockB.mockClear();
environment.mock.resolve(TestQuery, response);
Expand Down Expand Up @@ -1384,7 +1401,7 @@ describe('ReactRelayQueryRenderer', () => {
});

it('renders the query results', () => {
expect.assertions(3);
expect.assertions(2);
render.mockClear();
environment.mock.resolve(TestQuery, response);
const owner = createOperationDescriptor(TestQuery, variables);
Expand Down Expand Up @@ -1848,7 +1865,7 @@ describe('ReactRelayQueryRenderer', () => {
render.mockClear();
environment.mock.resolve(TestQuery, response);

expect(render).toBeCalledTimes(2);
expect(render).toBeCalledTimes(1);
const readyState = render.mock.calls[0][0];
expect(readyState.retry).not.toBe(null);
environment.mockClear();
Expand Down Expand Up @@ -1888,7 +1905,7 @@ describe('ReactRelayQueryRenderer', () => {
render.mockClear();
environment.mock.resolve(TestQuery, response);

expect(render).toBeCalledTimes(2);
expect(render).toBeCalledTimes(1);
const readyState = render.mock.calls[0][0];
expect(readyState.retry).not.toBe(null);
environment.mockClear();
Expand Down Expand Up @@ -1916,7 +1933,7 @@ describe('ReactRelayQueryRenderer', () => {
render.mockClear();
environment.mock.resolve(TestQuery, response);

expect(render).toBeCalledTimes(2);
expect(render).toBeCalledTimes(1);
const readyState = render.mock.calls[0][0];
expect(readyState.retry).not.toBe(null);
environment.mockClear();
Expand Down Expand Up @@ -1997,7 +2014,7 @@ describe('ReactRelayQueryRenderer', () => {
onResponse={onResponse}
/>,
);
expect.assertions(4);
expect.assertions(3);
render.mockClear();
environment.mock.resolve(TestQuery, responseErrors);
const owner = createOperationDescriptor(TestQuery, variables);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ function fetchQuery(operation, variables, _cacheConfig, _uploadables): Promise<a
query: operation.text, // GraphQL text from input
variables,
}),
}).then((response) => response.json()),
}).then((response) => {
return response.json().then((result) => {
/*result.extensions = {
is_final: true,
};*/
return result;
});
}),
);
}

Expand Down
5 changes: 5 additions & 0 deletions examples/relay-hook-example/pagination-nextjs-ssr/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ app.prepare().then(() => {
schema,
graphiql: false,
pretty: true,
extensions: () => {
return {
is_final: true
}
}
}),
);

Expand Down
20 changes: 16 additions & 4 deletions src/FetchResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ export type Fetcher = {
operation: OperationDescriptor,
fetchPolicy: FetchPolicy | null | undefined,
onComplete: (_e: Error | null) => void,
onNext: (operation: OperationDescriptor, snapshot: Snapshot, fromStore?: boolean, onlyStore?: boolean) => void,
onNext: (
operation: OperationDescriptor,
snapshot: Snapshot,
response?: GraphQLResponse,
fromStore?: boolean,
onlyStore?: boolean,
) => void,
onResponse?: (response: GraphQLResponse | null) => void,
renderPolicy?: RenderPolicy,
) => Disposable;
Expand Down Expand Up @@ -104,7 +110,13 @@ export function fetchResolver({
operation: OperationDescriptor,
fetchPolicy: FetchPolicy = 'network-only',
onComplete = (_e: Error | null): void => undefined,
onNext: (operation: OperationDescriptor, snapshot: Snapshot, fromStore?: boolean, onlyStore?: boolean) => void,
onNext: (
operation: OperationDescriptor,
snapshot: Snapshot,
response?: GraphQLResponse,
fromStore?: boolean,
onlyStore?: boolean,
) => void,
onResponse?: (response: GraphQLResponse | null) => void,
renderPolicy?: RenderPolicy,
): Disposable => {
Expand All @@ -122,7 +134,7 @@ export function fetchResolver({
const isNetwork = isNetworkPolicy(fetchPolicy, full);
if (snapshot != null) {
const onlyStore = !isNetwork;
onNext(operation, snapshot, true, onlyStore);
onNext(operation, snapshot, null, true, onlyStore);
if (onlyStore) {
onComplete(null);
}
Expand Down Expand Up @@ -166,7 +178,7 @@ export function fetchResolver({
operation.request.cacheConfig?.poll && updateLoading(false);
resolveNetworkPromise();
onResponse && onResponse(response);
onNext(operation, store);
onNext(operation, store, response);
},
start: (subscription) => {
refetchSubscription = subscription;
Expand Down
7 changes: 5 additions & 2 deletions src/QueryFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
OperationDescriptor,
GraphQLTaggedNode,
Variables,
GraphQLResponse,
} from 'relay-runtime';
import { Fetcher, fetchResolver } from './FetchResolver';
import { FetchPolicy, RenderProps, QueryOptions, Options } from './RelayHooksTypes';
Expand Down Expand Up @@ -116,12 +117,14 @@ export class QueryFetcher<TOperationType extends OperationType = OperationType>

const { onComplete, onResponse } = options;
let fetchHasReturned = false;
const onNext = (_o: OperationDescriptor, snapshot: Snapshot): void => {
const onNext = (_o: OperationDescriptor, snapshot: Snapshot, response: GraphQLResponse): void => {
if (!this.snapshot) {
this.snapshot = snapshot;
this.subscribe(snapshot);
this.resolveResult();
if (fetchHasReturned) {
const responses = Array.isArray(response) ? response : [response];
const isFinal = responses.some((x) => x != null && x.extensions?.is_final === true);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deepening the proposed solution for defer facebook/relay#3121 (comment)

prefer to change the logic using hasNext (spec-complaint) instead of is_final (FB specific):

const isIncremental = responses.some((x) => x != null && x.hasNext === true);
if (fetchHasReturned && isIncremental) {
    this.forceUpdate();
}

In this way, no code modification is needed to optimize the render in case an incremental query is not used but it could be a breaking change for those using it with other specifications

if (fetchHasReturned && !isFinal) {
this.forceUpdate();
}
}
Expand Down