@apollo/client
Version:
A fully-featured caching GraphQL client.
149 lines (148 loc) • 6.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useLazyQuery = useLazyQuery;
const tslib_1 = require("tslib");
const equality_1 = require("@wry/equality");
const React = tslib_1.__importStar(require("react"));
const client_1 = require("@apollo/client");
const internal_1 = require("@apollo/client/utilities/internal");
const invariant_1 = require("@apollo/client/utilities/invariant");
const index_js_1 = require("./internal/index.cjs");
const useDeepMemo_js_1 = require("./internal/useDeepMemo.cjs");
const useIsomorphicLayoutEffect_js_1 = require("./internal/useIsomorphicLayoutEffect.cjs");
const useApolloClient_js_1 = require("./useApolloClient.cjs");
const useSyncExternalStore_js_1 = require("./useSyncExternalStore.cjs");
// 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",
];
function useLazyQuery(query, options) {
const client = (0, useApolloClient_js_1.useApolloClient)(options?.client);
const previousDataRef = React.useRef(undefined);
const resultRef = React.useRef(undefined);
const stableOptions = (0, useDeepMemo_js_1.useDeepMemo)(() => options, [options]);
const calledDuringRender = (0, index_js_1.useRenderGuard)();
function createObservable() {
return client.watchQuery({
...options,
query,
initialFetchPolicy: options?.fetchPolicy,
fetchPolicy: "standby",
[internal_1.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 && !(0, equality_1.equal)(previousData, result.data)) {
previousDataRef.current = previousData;
}
resultRef.current = result;
forceUpdate();
}, []);
const observableResult = (0, useSyncExternalStore_js_1.useSyncExternalStore)(React.useCallback((forceUpdate) => {
const subscription = observable.subscribe((result) => {
if (!(0, equality_1.equal)(resultRef.current, result)) {
updateResult(result, forceUpdate);
}
});
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 = React.useMemo(() => {
const eagerMethods = {};
for (const key of EAGER_METHODS) {
eagerMethods[key] = function (...args) {
(0, invariant_1.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_1.invariant)(!calledDuringRender(), 30);
const [executeOptions] = args;
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);
(0, useIsomorphicLayoutEffect_js_1.useIsomorphicLayoutEffect)(() => {
executeRef.current = execute;
});
const stableExecute = React.useCallback((...args) => executeRef.current(...args), []);
const result = React.useMemo(() => {
const { partial, ...result } = observableResult;
return {
...eagerMethods,
...result,
client,
previousData: previousDataRef.current,
variables: observable.variables,
observable,
called: !!resultRef.current,
};
}, [client, observableResult, eagerMethods, observable]);
return [stableExecute, result];
}
const initialResult = (0, internal_1.maybeDeepFreeze)({
data: undefined,
dataState: "empty",
loading: false,
networkStatus: client_1.NetworkStatus.ready,
partial: true,
});
//# sourceMappingURL=useLazyQuery.cjs.map