Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Add useQuery lazy mode to defer query execution #3214

Merged
merged 15 commits into from
Jul 17, 2019
Merged
Show file tree
Hide file tree
Changes from 5 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
17 changes: 16 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/common/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export interface QueryResult<TData = any, TVariables = OperationVariables>
error?: ApolloError;
loading: boolean;
networkStatus: NetworkStatus;
called: boolean;
}

/* Mutation types */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

exports[`Query component calls the children prop: result in render prop 1`] = `
Object {
"called": true,
"data": Object {
"allPeople": Object {
"people": Array [
Expand All @@ -19,6 +20,7 @@ Object {

exports[`Query component calls the children prop: result in render prop while loading 1`] = `
Object {
"called": true,
"data": Object {},
"error": undefined,
"fetchMore": [Function],
Expand Down
119 changes: 94 additions & 25 deletions packages/hooks/src/data/QueryData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,43 @@ import {
import {
QueryPreviousData,
QueryOptions,
QueryCurrentObservable
QueryCurrentObservable,
QueryTuple,
QueryLazyOptions
} from '../types';
import { OperationData } from './OperationData';

export class QueryData<TData, TVariables> extends OperationData {
export class QueryData<TData, TVariables, TLazy> extends OperationData {
private previousData: QueryPreviousData<TData, TVariables> = {};
private currentObservable: QueryCurrentObservable<TData, TVariables> = {};
private forceUpdate: any;

private runLazy: boolean = false;
private lazyOptions?: QueryLazyOptions<TVariables>;

constructor({
options,
context,
forceUpdate
}: {
options: QueryOptions<TData, TVariables>;
options: QueryOptions<TData, TVariables, TLazy>;
context: ApolloContextValue;
forceUpdate: any;
}) {
super(options, context);
this.forceUpdate = forceUpdate;
}

public execute(): QueryResult<TData, TVariables> {
public execute(): TLazy extends boolean
? QueryTuple<TData, TVariables>
: QueryResult<TData, TVariables> {
this.refreshClient();

const { lazy } = this.getOptions();
if (lazy && !this.runLazy) {
return this.initialLazyResult() as any;
}

const { skip, query } = this.getOptions();
if (skip || query !== this.previousData.query) {
this.removeQuerySubscription();
Expand All @@ -50,24 +62,7 @@ export class QueryData<TData, TVariables> extends OperationData {

if (this.isMounted) this.startQuerySubscription();

const finish = () => {
const result = this.getQueryResult();
this.startQuerySubscription();
return result;
};

if (this.context && this.context.renderPromises) {
const result = this.context.renderPromises.addQueryPromise(this, finish);
return (
result || {
loading: true,
networkStatus: NetworkStatus.loading,
data: {}
}
);
}

return finish();
return this.getExecuteSsrResult() || this.getExecuteResult();
}

// For server-side rendering
Expand Down Expand Up @@ -108,7 +103,9 @@ export class QueryData<TData, TVariables> extends OperationData {

public afterExecute() {
this.isMounted = true;
this.handleErrorOrCompleted();
if (!this.getOptions().lazy || this.runLazy) {
this.handleErrorOrCompleted();
}
return this.unmount.bind(this);
}

Expand All @@ -118,6 +115,77 @@ export class QueryData<TData, TVariables> extends OperationData {
delete this.previousData.result;
}

public getOptions() {
const options = super.getOptions();
const lazyOptions = this.lazyOptions || {};
const updatedOptions = {
...options,
variables: {
...options.variables,
...lazyOptions.variables
},
context: {
...options.context,
...lazyOptions.context
}
};

// Triggering a query in lazy mode overrides `skip` settings.
if (options.lazy) {
updatedOptions.skip = false;
}

return updatedOptions;
}

private initialLazyResult(): QueryTuple<TData, TVariables> {
return [
{
loading: false,
networkStatus: NetworkStatus.ready,
called: false,
data: undefined
} as QueryResult<TData, TVariables>,
this.executeLazy.bind(this)
];
}

private executeLazy(options?: QueryLazyOptions<TVariables>) {
this.runLazy = true;
this.lazyOptions = options;
this.forceUpdate();
}

private getExecuteResult(): TLazy extends boolean
? QueryTuple<TData, TVariables>
: QueryResult<TData, TVariables> {
const result = this.getQueryResult();
this.startQuerySubscription();
return (this.getOptions().lazy !== undefined
? [result, this.executeLazy.bind(this)]
: result) as any;
}

private getExecuteSsrResult() {
let result;
if (this.context && this.context.renderPromises) {
result = this.context.renderPromises.addQueryPromise(
this,
this.getExecuteResult.bind(this)
) || {
loading: true,
networkStatus: NetworkStatus.loading,
called: true,
data: {}
};

if (this.getOptions().lazy !== undefined) {
result = [result, this.executeLazy.bind(this)];
}
}
return result;
}

private updateCurrentData() {
if (this.isMounted) {
this.forceUpdate();
Expand Down Expand Up @@ -264,7 +332,8 @@ export class QueryData<TData, TVariables> extends OperationData {
...result,
data: undefined,
error: undefined,
loading: false
loading: false,
called: true
};
} else {
// Fetch the current result (if any) from the store.
Expand All @@ -279,7 +348,7 @@ export class QueryData<TData, TVariables> extends OperationData {
error = new ApolloError({ graphQLErrors: errors });
}

Object.assign(result, { loading, networkStatus, error });
Object.assign(result, { loading, networkStatus, error, called: true });

if (loading) {
const previousData = this.previousData.result
Expand Down
12 changes: 6 additions & 6 deletions packages/hooks/src/ssr/RenderPromises.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ObservableQuery } from "apollo-client";
import { QueryOptions } from "../types";
import { DocumentNode } from "graphql";
import { QueryData } from "../data/QueryData";
import { ObservableQuery } from 'apollo-client';
import { QueryOptions } from '../types';
import { DocumentNode } from 'graphql';
import { QueryData } from '../data/QueryData';

type QueryInfo = {
seen: boolean;
Expand Down Expand Up @@ -40,8 +40,8 @@ export class RenderPromises {
return this.lookupQueryInfo(props).observable;
}

public addQueryPromise<TData, TVariables>(
queryInstance: QueryData<TData, TVariables>,
public addQueryPromise<TData, TVariables, TLazy>(
queryInstance: QueryData<TData, TVariables, TLazy>,
finish: () => React.ReactNode
): React.ReactNode {
const info = this.lookupQueryInfo(queryInstance.getOptions());
Expand Down
29 changes: 24 additions & 5 deletions packages/hooks/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
MutationFunctionOptions,
ExecutionResult,
BaseSubscriptionOptions,
SubscriptionResult
SubscriptionResult,
Context
} from '@apollo/react-common';
import { DocumentNode } from 'graphql';

Expand All @@ -26,15 +27,23 @@ export type CommonOptions<TOptions> = TOptions & {

/* Query types */

export interface QueryOptions<TData = any, TVariables = OperationVariables>
extends QueryFunctionOptions<TData, TVariables> {
export interface QueryOptions<
TData = any,
TVariables = OperationVariables,
TLazy = undefined
> extends QueryFunctionOptions<TData, TVariables> {
children?: (result: QueryResult<TData, TVariables>) => ReactNode;
query: DocumentNode;
lazy?: TLazy;
}

export interface QueryHookOptions<TData = any, TVariables = OperationVariables>
extends QueryFunctionOptions<TData, TVariables> {
export interface QueryHookOptions<
TData = any,
TVariables = OperationVariables,
TLazy = undefined
> extends QueryFunctionOptions<TData, TVariables> {
query?: DocumentNode;
lazy?: TLazy;
}

export interface QueryPreviousData<TData, TVariables> {
Expand All @@ -51,6 +60,16 @@ export interface QueryCurrentObservable<TData, TVariables> {
subscription?: ZenObservable.Subscription;
}

export interface QueryLazyOptions<TVariables> {
variables?: TVariables;
context?: Context;
}

export type QueryTuple<TData, TVariables> = [
QueryResult<TData, TVariables>,
(options?: QueryLazyOptions<TVariables>) => void
];

/* Mutation types */

export interface MutationHookOptions<
Expand Down
20 changes: 13 additions & 7 deletions packages/hooks/src/useQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,29 @@ import {
} from '@apollo/react-common';
import { DocumentNode } from 'graphql';

import { QueryHookOptions, QueryOptions } from './types';
import { QueryHookOptions, QueryOptions, QueryTuple } from './types';
import { QueryData } from './data/QueryData';
import { useDeepMemo } from './utils/useDeepMemo';

export function useQuery<TData = any, TVariables = OperationVariables>(
export function useQuery<
TData = any,
TVariables = OperationVariables,
TLazy = undefined
>(
query: DocumentNode,
options?: QueryHookOptions<TData, TVariables>
): QueryResult<TData, TVariables> {
options?: QueryHookOptions<TData, TVariables, TLazy>
): TLazy extends boolean
? QueryTuple<TData, TVariables>
: QueryResult<TData, TVariables> {
const context = useContext(getApolloContext());
const [tick, forceUpdate] = useReducer(x => x + 1, 0);
const updatedOptions = options ? { ...options, query } : { query };

const queryDataRef = useRef<QueryData<TData, TVariables>>();
const queryDataRef = useRef<QueryData<TData, TVariables, TLazy>>();
function getQueryDataRef() {
if (!queryDataRef.current) {
queryDataRef.current = new QueryData<TData, TVariables>({
options: updatedOptions as QueryOptions<TData, TVariables>,
queryDataRef.current = new QueryData<TData, TVariables, TLazy>({
options: updatedOptions as QueryOptions<TData, TVariables, TLazy>,
context,
forceUpdate
});
Expand Down