Last active
January 3, 2019 19:04
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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