@apollo/client
Version:
A fully-featured caching GraphQL client.
103 lines • 5 kB
JavaScript
import { equal } from "@wry/equality";
import * as React from "react";
import { useDeepMemo, wrapHook } from "./internal/index.js";
import { useApolloClient } from "./useApolloClient.js";
import { useSyncExternalStore } from "./useSyncExternalStore.js";
/**
* `useFragment` represents a lightweight live binding into the Apollo Client Cache and enables Apollo Client to broadcast very specific fragment results to individual components. This hook returns an always-up-to-date view of whatever data the cache currently contains for a given fragment. `useFragment` never triggers network requests of its own.
*
* Note that the `useQuery` hook remains the primary hook responsible for querying and populating data in the cache ([see the API reference](./hooks#usequery)). As a result, the component reading the fragment data via `useFragment` is still subscribed to all changes in the query data, but receives updates only when that fragment's specific data change.
*
* To view a `useFragment` example, see the [Fragments](https://www.apollographql.com/docs/react/data/fragments#usefragment) page.
*/
export function useFragment(options) {
"use no memo";
return wrapHook("useFragment",
// eslint-disable-next-line react-compiler/react-compiler
useFragment_, useApolloClient(options.client))(options);
}
function useFragment_(options) {
const client = useApolloClient(options.client);
const { cache } = client;
const { from, ...rest } = options;
// We calculate the cache id separately from `stableOptions` because we don't
// want changes to non key fields in the `from` property to affect
// `stableOptions` and retrigger our subscription. If the cache identifier
// stays the same between renders, we want to reuse the existing subscription.
const id = React.useMemo(() => typeof from === "string" ? from
: from === null ? null
: cache.identify(from), [cache, from]);
const stableOptions = useDeepMemo(() => ({ ...rest, from: id }), [rest, id]);
// Since .next is async, we need to make sure that we
// get the correct diff on the next render given new diffOptions
const diff = React.useMemo(() => {
const { fragment, fragmentName, from, optimistic = true } = stableOptions;
if (from === null) {
return {
result: diffToResult({
result: {},
complete: false,
}),
};
}
const { cache } = client;
const diff = cache.diff({
...stableOptions,
returnPartialData: true,
id: from,
query: cache["getFragmentDoc"](client["transform"](fragment), fragmentName),
optimistic,
});
return {
result: diffToResult({
...diff,
result: client["queryManager"].maskFragment({
fragment,
fragmentName,
// TODO: Revert to `diff.result` once `useFragment` supports `null` as
// valid return value
data: diff.result === null ? {} : diff.result,
}),
}),
};
}, [client, stableOptions]);
// Used for both getSnapshot and getServerSnapshot
const getSnapshot = React.useCallback(() => diff.result, [diff]);
return useSyncExternalStore(React.useCallback((forceUpdate) => {
let lastTimeout = 0;
const subscription = stableOptions.from === null ?
null
: client.watchFragment(stableOptions).subscribe({
next: (result) => {
// Avoid unnecessarily rerendering this hook for the initial result
// emitted from watchFragment which should be equal to
// `diff.result`.
if (equal(result, diff.result))
return;
diff.result = result;
// If we get another update before we've re-rendered, bail out of
// the update and try again. This ensures that the relative timing
// between useQuery and useFragment stays roughly the same as
// fixed in https://github.com/apollographql/apollo-client/pull/11083
clearTimeout(lastTimeout);
lastTimeout = setTimeout(forceUpdate);
},
});
return () => {
subscription?.unsubscribe();
clearTimeout(lastTimeout);
};
}, [client, stableOptions, diff]), getSnapshot, getSnapshot);
}
function diffToResult(diff) {
const result = {
data: diff.result,
complete: !!diff.complete,
dataState: diff.complete ? "complete" : "partial",
}; // TODO: Remove assertion once useFragment returns null
if (diff.missing) {
result.missing = diff.missing.missing;
}
return result;
}
//# sourceMappingURL=useFragment.js.map