Skip to content

Instantly share code, notes, and snippets.

@trojanowski
Last active January 3, 2019 19:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trojanowski/f7e4e75659d2de1166274ad8f0d06bb4 to your computer and use it in GitHub Desktop.
Save trojanowski/f7e4e75659d2de1166274ad8f0d06bb4 to your computer and use it in GitHub Desktop.
react-apollo-hools useQuery with HELPERS_FOR_SKIPPED_QUERIES https://github.com/trojanowski/react-apollo-hooks/pull/42
import {
ApolloCurrentResult,
ApolloQueryResult,
FetchMoreOptions,
FetchMoreQueryOptions,
FetchPolicy,
ObservableQuery,
OperationVariables,
QueryOptions,
WatchQueryOptions,
} from 'apollo-client';
import { DocumentNode } from 'graphql';
import { useEffect, useMemo, useState } from 'react';
import { useApolloClient } from './ApolloContext';
import {
getCachedObservableQuery,
invalidateCachedObservableQuery,
} from './queryCache';
import { Omit, objToKey } from './utils';
export interface QueryHookState<TData>
extends Pick<
ApolloCurrentResult<undefined | TData>,
'error' | 'errors' | 'loading' | 'partial'
> {
data?: TData;
}
export interface QueryHookOptions<TVariables>
extends Omit<QueryOptions<TVariables>, 'query'> {
// watch query options from apollo client
notifyOnNetworkStatusChange?: boolean;
pollInterval?: number;
// custom options of `useQuery` hook
skip?: boolean;
suspend?: boolean;
}
interface QueryHookHelpers<TData, TVariables>
extends Pick<
ObservableQuery<TData, TVariables>,
'refetch' | 'startPolling' | 'stopPolling' | 'updateQuery'
> {
fetchMore<K extends keyof TVariables>(
fetchMoreOptions: FetchMoreQueryOptions<TVariables, K> &
FetchMoreOptions<TData, TVariables>
): Promise<ApolloQueryResult<TData>>;
}
export interface QueryHookResult<TData, TVariables>
extends QueryHookState<TData>,
QueryHookHelpers<TData, TVariables> {}
const HELPERS_FOR_SKIPPED_QUERIES = [
'refetch',
'startPolling',
'stopPolling',
'updateQuery',
].reduce((helpers: Record<string, (() => void)>, helperName: string) => {
helpers[helperName] = () => {
throw new Error(`Cannot invoke '${helperName}' on a skipped query`);
};
return helpers;
}, {});
export function useQuery<TData = any, TVariables = OperationVariables>(
query: DocumentNode,
{
// Hook options
skip = false,
suspend = true,
// Watch options
pollInterval,
notifyOnNetworkStatusChange,
// Apollo client options
context,
metadata,
variables,
fetchPolicy,
errorPolicy,
fetchResults,
}: QueryHookOptions<TVariables> = {}
): QueryHookResult<TData, TVariables> {
const client = useApolloClient();
const watchQueryOptions: WatchQueryOptions<TVariables> = useMemo(
() => ({
context,
errorPolicy,
fetchPolicy,
fetchResults,
metadata,
notifyOnNetworkStatusChange,
pollInterval,
query,
variables,
}),
[
query,
pollInterval,
notifyOnNetworkStatusChange,
context && objToKey(context),
metadata && objToKey(metadata),
variables && objToKey(variables),
fetchPolicy,
errorPolicy,
fetchResults,
]
);
const observableQuery = useMemo(
() =>
getCachedObservableQuery<TData, TVariables>(client, watchQueryOptions),
[client, watchQueryOptions]
);
const [responseId, setResponseId] = useState(0);
const currentResult = useMemo<QueryHookState<TData>>(
() => {
const result = observableQuery.currentResult();
return {
data: result.data as TData,
error: result.error,
errors: result.errors,
loading: result.loading,
partial: result.partial,
};
},
[skip, responseId, observableQuery]
);
useEffect(
() => {
if (skip) {
return;
}
const invalidateCurrentResult = () => setResponseId(x => x + 1);
const subscription = observableQuery.subscribe(
invalidateCurrentResult,
invalidateCurrentResult
);
invalidateCachedObservableQuery(client, watchQueryOptions);
return () => {
subscription.unsubscribe();
};
},
[skip, observableQuery]
);
ensureSupportedFetchPolicy(suspend, fetchPolicy);
if (skip) {
// Taken from https://github.com/apollographql/react-apollo/blob/5cb63b3625ce5e4a3d3e4ba132eaec2a38ef5d90/src/Query.tsx#L376-L381
return {
...(HELPERS_FOR_SKIPPED_QUERIES as QueryHookHelpers<TData, TVariables>),
data: undefined,
error: undefined,
loading: false,
};
}
const helpers = {
fetchMore: observableQuery.fetchMore.bind(observableQuery),
refetch: observableQuery.refetch.bind(observableQuery),
startPolling: observableQuery.startPolling.bind(observableQuery),
stopPolling: observableQuery.stopPolling.bind(observableQuery),
updateQuery: observableQuery.updateQuery.bind(observableQuery),
};
if (suspend && currentResult.partial) {
// throw a promise - use the react suspense to wait until the data is
// available
throw observableQuery.result();
}
return { ...helpers, ...currentResult };
}
function ensureSupportedFetchPolicy(
suspend: boolean,
fetchPolicy?: FetchPolicy
) {
if (suspend && fetchPolicy && fetchPolicy !== 'cache-first') {
throw new Error(
`Fetch policy ${fetchPolicy} is not supported without 'suspend: false'`
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment