UNPKG

@apollo/client

Version:

A fully-featured caching GraphQL client.

103 lines 5 kB
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