UNPKG

@apollo/client

Version:

A fully-featured caching GraphQL client.

381 lines (352 loc) 10.9 kB
import { c as _c } from "@apollo/client/react/internal/compiler-runtime"; import { equal } from "@wry/equality"; import * as React from "react"; import { invariant } from "@apollo/client/utilities/invariant"; import { skipToken } from "./constants.js"; import { useDeepMemo } from "./internal/useDeepMemo.js"; import { useIsomorphicLayoutEffect } from "./internal/useIsomorphicLayoutEffect.js"; import { useApolloClient } from "./useApolloClient.js"; import { useSyncExternalStore } from "./useSyncExternalStore.js"; /** * > Refer to the [Subscriptions](https://www.apollographql.com/docs/react/data/subscriptions/) section for a more in-depth overview of `useSubscription`. * * @example * * ```jsx * const COMMENTS_SUBSCRIPTION = gql` * subscription OnCommentAdded($repoFullName: String!) { * commentAdded(repoFullName: $repoFullName) { * id * content * } * } * `; * * function DontReadTheComments({ repoFullName }) { * const { * data: { commentAdded }, * loading, * } = useSubscription(COMMENTS_SUBSCRIPTION, { variables: { repoFullName } }); * return <h4>New comment: {!loading && commentAdded.content}</h4>; * } * ``` * * @remarks * * #### Consider using `onData` instead of `useEffect` * * If you want to react to incoming data, please use the `onData` option instead of `useEffect`. * State updates you make inside a `useEffect` hook might cause additional rerenders, and `useEffect` is mostly meant for side effects of rendering, not as an event handler. * State updates made in an event handler like `onData` might - depending on the React version - be batched and cause only a single rerender. * * Consider the following component: * * ```jsx * export function Subscriptions() { * const { data, error, loading } = useSubscription(query); * const [accumulatedData, setAccumulatedData] = useState([]); * * useEffect(() => { * setAccumulatedData((prev) => [...prev, data]); * }, [data]); * * return ( * <> * {loading && <p>Loading...</p>} * {JSON.stringify(accumulatedData, undefined, 2)} * </> * ); * } * ``` * * Instead of using `useEffect` here, we can re-write this component to use the `onData` callback function accepted in `useSubscription`'s `options` object: * * ```jsx * export function Subscriptions() { * const [accumulatedData, setAccumulatedData] = useState([]); * const { data, error, loading } = useSubscription(query, { * onData({ data }) { * setAccumulatedData((prev) => [...prev, data.data]); * }, * }); * * return ( * <> * {loading && <p>Loading...</p>} * {JSON.stringify(accumulatedData, undefined, 2)} * </> * ); * } * ``` * * > ⚠️ **Note:** The `useSubscription` option `onData` is available in Apollo Client >= 3.7. In previous versions, the equivalent option is named `onSubscriptionData`. * * Now, the first message will be added to the `accumulatedData` array since `onData` is called _before_ the component re-renders. React 18 automatic batching is still in effect and results in a single re-render, but with `onData` we can guarantee each message received after the component mounts is added to `accumulatedData`. * * @param subscription - A GraphQL subscription document parsed into an AST by `gql`. * @param options - Options to control how the subscription is executed. * @returns Query result object */ export function useSubscription(subscription, ...t0) { const $ = _c(38); const [t1] = t0; let t2; if ($[0] !== t1) { t2 = t1 === undefined ? {} : t1; $[0] = t1; $[1] = t2; } else { t2 = $[1]; } const _options = t2; let t3; if ($[2] !== _options) { t3 = _options === skipToken ? { skip: true } : _options; $[2] = _options; $[3] = t3; } else { t3 = $[3]; } const options = t3; const client = useApolloClient(options.client); const { skip, fetchPolicy, errorPolicy, shouldResubscribe, context, extensions, ignoreResults } = options; let t4; let t5; if ($[4] !== options.variables) { t4 = () => options.variables;t5 = [options.variables]; $[4] = options.variables; $[5] = t4; $[6] = t5; } else { t4 = $[5]; t5 = $[6]; } const variables = useDeepMemo(t4, t5); let t6; if ($[7] !== client || $[8] !== context || $[9] !== errorPolicy || $[10] !== extensions || $[11] !== fetchPolicy || $[12] !== subscription || $[13] !== variables) { t6 = () => createSubscription( client, subscription, variables, fetchPolicy, errorPolicy, context, extensions ); $[7] = client; $[8] = context; $[9] = errorPolicy; $[10] = extensions; $[11] = fetchPolicy; $[12] = subscription; $[13] = variables; $[14] = t6; } else { t6 = $[14]; } const recreate = t6; const [t7, setObservable] = React.useState(skip ? null : recreate); let observable = t7; const recreateRef = React.useRef(recreate); let t8; if ($[15] !== recreate) { t8 = () => { recreateRef.current = recreate; }; $[15] = recreate; $[16] = t8; } else { t8 = $[16]; } useIsomorphicLayoutEffect(t8); if (skip) { if (observable) { setObservable(observable = null); } } else { if (!observable || (client !== observable.__.client || subscription !== observable.__.query || fetchPolicy !== observable.__.fetchPolicy || errorPolicy !== observable.__.errorPolicy || !equal(variables, observable.__.variables)) && (typeof shouldResubscribe === "function" ? !!shouldResubscribe(options) : shouldResubscribe) !== false) { setObservable(observable = recreate()); } } const optionsRef = React.useRef(options); let t9; if ($[17] !== options) { t9 = () => { optionsRef.current = options; }; $[17] = options; $[18] = t9; } else { t9 = $[18]; } React.useEffect(t9); const fallbackLoading = !skip && !ignoreResults; let t10; if ($[19] !== fallbackLoading) { t10 = { loading: fallbackLoading, error: void 0, data: void 0 }; $[19] = fallbackLoading; $[20] = t10; } else { t10 = $[20]; } const fallbackResult = t10; const ignoreResultsRef = React.useRef(ignoreResults); let t11; if ($[21] !== ignoreResults) { t11 = () => { ignoreResultsRef.current = ignoreResults; }; $[21] = ignoreResults; $[22] = t11; } else { t11 = $[22]; } useIsomorphicLayoutEffect(t11); let t12; if ($[23] !== observable) { t12 = update => { if (!observable) { return _temp; } let subscriptionStopped = false; const client_0 = observable.__.client; const subscription_0 = observable.subscribe({ next(value) { if (subscriptionStopped) { return; } const result = { loading: false, data: value.data, error: value.error }; observable.__.setResult(result); if (!ignoreResultsRef.current) { update(); } if (result.error) { optionsRef.current.onError?.(result.error); } else { if (optionsRef.current.onData) { optionsRef.current.onData({ client: client_0, data: result }); } } }, complete() { observable.__.completed = true; if (!subscriptionStopped && optionsRef.current.onComplete) { optionsRef.current.onComplete(); } } }); return () => { subscriptionStopped = true; setTimeout(() => subscription_0.unsubscribe()); }; }; $[23] = observable; $[24] = t12; } else { t12 = $[24]; } observable; let t13; if ($[25] !== fallbackResult || $[26] !== ignoreResults || $[27] !== observable || $[28] !== skip) { t13 = () => observable && !skip && !ignoreResults ? observable.__.result : fallbackResult; $[25] = fallbackResult; $[26] = ignoreResults; $[27] = observable; $[28] = skip; $[29] = t13; } else { t13 = $[29]; } let t14; if ($[30] !== fallbackResult) { t14 = () => fallbackResult; $[30] = fallbackResult; $[31] = t14; } else { t14 = $[31]; } const ret = useSyncExternalStore(t12, t13, t14); let t15; if ($[32] !== observable || $[33] !== setObservable) { t15 = () => { invariant(!optionsRef.current.skip, 34); if (observable?.__.completed) { setObservable(recreateRef.current()); } else { observable?.restart(); } }; $[32] = observable; $[33] = setObservable; $[34] = t15; } else { t15 = $[34]; } observable; const restart = t15; let t16; if ($[35] !== restart || $[36] !== ret) { t16 = { ...ret, restart }; $[35] = restart; $[36] = ret; $[37] = t16; } else { t16 = $[37]; } return t16; } function _temp() {} function createSubscription(client, query, variables, fetchPolicy, errorPolicy, context, extensions) { const options = { query, variables, fetchPolicy, errorPolicy, context, extensions, }; const __ = { ...options, client, completed: false, result: { loading: true, data: void 0, error: void 0, }, setResult(result) { __.result = result; }, }; return Object.assign(client.subscribe(options), { /** * A tracking object to store details about the observable and the latest result of the subscription. */ __, }); } //# sourceMappingURL=useSubscription.js.map