@apollo/client
Version:
A fully-featured caching GraphQL client.
145 lines (144 loc) • 6.14 kB
JavaScript
import { equal } from "@wry/equality";
import * as React from "react";
import { NetworkStatus } from "@apollo/client";
import { maybeDeepFreeze, variablesUnknownSymbol, } from "@apollo/client/utilities/internal";
import { invariant } from "@apollo/client/utilities/invariant";
import { useRenderGuard } from "./internal/index.js";
import { useDeepMemo } from "./internal/useDeepMemo.js";
import { useIsomorphicLayoutEffect } from "./internal/useIsomorphicLayoutEffect.js";
import { useApolloClient } from "./useApolloClient.js";
import { useSyncExternalStore } from "./useSyncExternalStore.js";
// The following methods, when called will execute the query, regardless of
// whether the useLazyQuery execute function was called before.
const EAGER_METHODS = [
"refetch",
"fetchMore",
"updateQuery",
"startPolling",
"stopPolling",
"subscribeToMore",
];
export function useLazyQuery(query, options) {
const client = useApolloClient(options?.client);
const previousDataRef = React.useRef(undefined);
const resultRef = React.useRef(undefined);
const stableOptions = useDeepMemo(() => options, [options]);
const calledDuringRender = useRenderGuard();
function createObservable() {
return client.watchQuery({
...options,
query,
initialFetchPolicy: options?.fetchPolicy,
fetchPolicy: "standby",
[variablesUnknownSymbol]: true,
});
}
const [currentClient, setCurrentClient] = React.useState(client);
const [observable, setObservable] = React.useState(createObservable);
if (currentClient !== client) {
setCurrentClient(client);
setObservable(createObservable());
}
// TODO: Revisit after we have RxJS in place. We should be able to use
// observable.getCurrentResult() (or equivalent) to get these values which
// will hopefully alleviate the need for us to use refs to track these values.
const updateResult = React.useCallback((result, forceUpdate) => {
const previousData = resultRef.current?.data;
if (previousData && !equal(previousData, result.data)) {
previousDataRef.current = previousData;
}
resultRef.current = result;
forceUpdate();
}, []);
const observableResult = useSyncExternalStore(React.useCallback((forceUpdate_0) => {
const subscription = observable.subscribe((result_0) => {
if (!equal(resultRef.current, result_0)) {
updateResult(result_0, forceUpdate_0);
}
});
return () => {
subscription.unsubscribe();
};
}, [observable, updateResult]), () => resultRef.current || initialResult, () => initialResult);
// We use useMemo here to make sure the eager methods have a stable identity.
const eagerMethods_0 = React.useMemo(() => {
const eagerMethods = {};
for (const key of EAGER_METHODS) {
eagerMethods[key] = function (...args) {
invariant(resultRef.current, 29, key);
// @ts-expect-error this is just to generic to type
return observable[key](...args);
};
}
return eagerMethods;
}, [observable]);
React.useEffect(() => {
const updatedOptions = {
query,
errorPolicy: stableOptions?.errorPolicy,
refetchWritePolicy: stableOptions?.refetchWritePolicy,
returnPartialData: stableOptions?.returnPartialData,
notifyOnNetworkStatusChange: stableOptions?.notifyOnNetworkStatusChange,
nextFetchPolicy: options?.nextFetchPolicy,
skipPollAttempt: options?.skipPollAttempt,
};
// Wait to apply the changed fetch policy until after the execute
// function has been called. The execute function will handle setting the
// the fetch policy away from standby for us when called for the first time.
if (observable.options.fetchPolicy !== "standby" &&
stableOptions?.fetchPolicy) {
updatedOptions.fetchPolicy = stableOptions.fetchPolicy;
}
observable.applyOptions(updatedOptions);
}, [
query,
observable,
stableOptions,
// Ensure inline functions don't suffer from stale closures by checking for
// these deps separately. @wry/equality doesn't compare function identity
// so `stableOptions` isn't updated when using inline functions.
options?.nextFetchPolicy,
options?.skipPollAttempt,
]);
const execute = React.useCallback((...args_0) => {
invariant(!calledDuringRender(), 30);
const [executeOptions] = args_0;
let fetchPolicy = observable.options.fetchPolicy;
if (fetchPolicy === "standby") {
fetchPolicy = observable.options.initialFetchPolicy;
}
return observable.reobserve({
fetchPolicy,
// If `variables` is not given, reset back to empty variables by
// ensuring the key exists in options
variables: executeOptions?.variables,
context: executeOptions?.context ?? {},
});
}, [observable, calledDuringRender]);
const executeRef = React.useRef(execute);
useIsomorphicLayoutEffect(() => {
executeRef.current = execute;
});
const stableExecute = React.useCallback((...args_1) => executeRef.current(...args_1), []);
const result_2 = React.useMemo(() => {
const { partial, ...result_1 } = observableResult;
return {
...eagerMethods_0,
...result_1,
client,
previousData: previousDataRef.current,
variables: observable.variables,
observable,
called: !!resultRef.current,
};
}, [client, observableResult, eagerMethods_0, observable]);
return [stableExecute, result_2];
}
const initialResult = maybeDeepFreeze({
data: undefined,
dataState: "empty",
loading: false,
networkStatus: NetworkStatus.ready,
partial: true,
});
//# sourceMappingURL=useLazyQuery.js.map