UNPKG

@shopify/react-graphql

Version:

Tools for creating type-safe and asynchronous GraphQL components for React

143 lines (142 loc) 5.76 kB
/* eslint react-hooks/rules-of-hooks: off */ import { useEffect, useMemo, useState, useRef } from 'react'; import { ApolloError, } from 'apollo-client'; import { useServerEffect } from '@shopify/react-effect'; import useApolloClient from './apollo-client'; import useGraphQLDocument from './graphql-document'; export default function useQuery(queryOrAsyncQuery, ...optionsPart) { const [options = {}] = optionsPart; const { skip = false, fetchPolicy, errorPolicy, pollInterval, client: overrideClient, notifyOnNetworkStatusChange, context, ssr = true, } = options; const variables = options.variables || {}; const client = useApolloClient(overrideClient); if (typeof window === 'undefined' && (skip || fetchPolicy === 'no-cache' || !ssr)) { return createDefaultResult(client, variables); } const query = useGraphQLDocument(queryOrAsyncQuery); const normalizedFetchPolicy = typeof window === 'undefined' && (fetchPolicy === 'network-only' || fetchPolicy === 'cache-and-network') ? 'cache-first' : fetchPolicy; const serializedVariables = variables && JSON.stringify(variables); const watchQueryOptions = useMemo(() => { if (!query) { return null; } return { query, context, variables, fetchPolicy: normalizedFetchPolicy, errorPolicy, pollInterval, notifyOnNetworkStatusChange, }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [ query, // eslint-disable-next-line react-hooks/exhaustive-deps context && JSON.stringify(context), serializedVariables, normalizedFetchPolicy, errorPolicy, pollInterval, notifyOnNetworkStatusChange, ]); const queryObservable = useMemo(() => { if (skip || !watchQueryOptions) { return; } return client.watchQuery(watchQueryOptions); }, [client, skip, watchQueryOptions]); useServerEffect(() => { if (queryObservable == null) { return; } const result = queryObservable.getCurrentResult(); return result.loading ? queryObservable.result() : undefined; }); const defaultResult = useMemo(() => createDefaultResult(client, variables, queryObservable), // eslint-disable-next-line react-hooks/exhaustive-deps [queryObservable, client, serializedVariables]); const [responseId, setResponseId] = useState(0); useEffect(() => { if (skip || !queryObservable) { return; } const invalidateCurrentResult = () => { setResponseId(x => x + 1); }; const subscription = queryObservable.subscribe(invalidateCurrentResult, invalidateCurrentResult); return () => { subscription.unsubscribe(); }; }, [skip, queryObservable]); const previousData = useRef(undefined); const currentResult = useMemo(() => { // must of the logic below are lifted from // https://github.com/apollographql/react-apollo/blob/22f8ebf52b26b348d6be905d5b7fbbfea51c1541/src/Query.tsx#L410-L477 if (skip) { return defaultResult; } if (!queryObservable) { return Object.assign(Object.assign({}, defaultResult), { // while documentNode is loading loading: true }); } const result = queryObservable.getCurrentResult(); const { fetchPolicy } = queryObservable.options; const hasError = result.errors && result.errors.length > 0; let data = result.data; if (result.loading) { data = previousData.current || (result && result.data) ? Object.assign(Object.assign({}, (previousData.current || {})), ((result && result.data) || {})) : undefined; } else if (hasError) { data = (queryObservable.getLastResult() || {}).data; } else if (fetchPolicy === 'no-cache' && (!result.data || Object.keys(result.data).length === 0)) { data = previousData.current; } else { previousData.current = result.data; } return Object.assign(Object.assign({}, defaultResult), { data, error: hasError ? new ApolloError({ graphQLErrors: result.errors }) : result.error, networkStatus: result.networkStatus, loading: result.loading }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [responseId, skip, queryObservable, defaultResult, previousData]); return currentResult; } function createDefaultResult(client, variables, queryObservable) { return { data: undefined, error: undefined, networkStatus: undefined, loading: false, called: false, variables: (queryObservable ? queryObservable.variables : variables), refetch: queryObservable ? queryObservable.refetch.bind(queryObservable) : noop, fetchMore: queryObservable ? queryObservable.fetchMore.bind(queryObservable) : noop, updateQuery: queryObservable ? queryObservable.updateQuery.bind(queryObservable) : noop, startPolling: queryObservable ? queryObservable.startPolling.bind(queryObservable) : noop, stopPolling: queryObservable ? queryObservable.stopPolling.bind(queryObservable) : noop, subscribeToMore: queryObservable ? queryObservable.subscribeToMore.bind(queryObservable) : noop, client, }; } function noop() { }