diff --git a/.changeset/large-beers-rhyme.md b/.changeset/large-beers-rhyme.md new file mode 100644 index 00000000000..392e4d1a199 --- /dev/null +++ b/.changeset/large-beers-rhyme.md @@ -0,0 +1,5 @@ +--- +'@apollo/client': patch +--- + +Use `import * as React` everywhere. This prevents an error when importing `@apollo/client` in a React Server component. (see [#10974](https://github.com/apollographql/apollo-client/issues/10974)) diff --git a/.eslintrc b/.eslintrc index 354cef8621d..02a8f6d9027 100644 --- a/.eslintrc +++ b/.eslintrc @@ -30,6 +30,13 @@ "fixStyle": "separate-type-imports" }], "@typescript-eslint/no-import-type-side-effects": "error", + "no-restricted-syntax": [ + "error", + { + "selector": "ImportDeclaration[source.value='react'][importKind!='type'] :matches(ImportSpecifier, ImportDefaultSpecifier)", + "message": "Please only use the namespace import syntax (`import * as React from 'react'`) for React imports!" + } + ], "import/extensions": [ "error", "always", diff --git a/.size-limit.cjs b/.size-limit.cjs index 8a21c5f0e40..625c82511aa 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -1,7 +1,7 @@ const checks = [ { path: "dist/apollo-client.min.cjs", - limit: "37976" + limit: "37860" }, { path: "dist/main.cjs", @@ -10,7 +10,7 @@ const checks = [ { path: "dist/index.js", import: "{ ApolloClient, InMemoryCache, HttpLink }", - limit: "33437" + limit: "32181" }, ...[ "ApolloProvider", diff --git a/src/react/hooks/internal/__use.ts b/src/react/hooks/internal/__use.ts index e8001c9c23c..0784816fa82 100644 --- a/src/react/hooks/internal/__use.ts +++ b/src/react/hooks/internal/__use.ts @@ -1,5 +1,5 @@ import { wrapPromiseWithState } from '../../../utilities/index.js'; -import React from 'react'; +import * as React from 'react'; type Use = (promise: Promise) => T; // Prevent webpack from complaining about our feature detection of the diff --git a/src/react/hooks/internal/useDeepMemo.ts b/src/react/hooks/internal/useDeepMemo.ts index 5f9c3cb2351..ac96206ce9b 100644 --- a/src/react/hooks/internal/useDeepMemo.ts +++ b/src/react/hooks/internal/useDeepMemo.ts @@ -1,12 +1,12 @@ import type { DependencyList } from 'react'; -import { useRef } from 'react'; +import * as React from 'react'; import { equal } from '@wry/equality'; export function useDeepMemo( memoFn: () => TValue, deps: DependencyList ) { - const ref = useRef<{ deps: DependencyList; value: TValue }>(); + const ref = React.useRef<{ deps: DependencyList; value: TValue }>(); if (!ref.current || !equal(ref.current.deps, deps)) { ref.current = { value: memoFn(), deps }; diff --git a/src/react/hooks/internal/useIsomorphicLayoutEffect.ts b/src/react/hooks/internal/useIsomorphicLayoutEffect.ts index 27c81fbd72c..7d4acd9b4d7 100644 --- a/src/react/hooks/internal/useIsomorphicLayoutEffect.ts +++ b/src/react/hooks/internal/useIsomorphicLayoutEffect.ts @@ -1,4 +1,4 @@ -import { useLayoutEffect, useEffect } from 'react'; +import * as React from 'react'; import { canUseDOM } from '../../../utilities/index.js'; // use canUseDOM here instead of canUseLayoutEffect because we want to be able @@ -7,5 +7,5 @@ import { canUseDOM } from '../../../utilities/index.js'; // warnings for useSyncExternalStore implementation, canUseLayoutEffect is left // alone. export const useIsomorphicLayoutEffect = canUseDOM - ? useLayoutEffect - : useEffect; + ? React.useLayoutEffect + : React.useEffect; diff --git a/src/react/hooks/internal/useStrictModeSafeCleanupEffect.ts b/src/react/hooks/internal/useStrictModeSafeCleanupEffect.ts index 6c4528fd20b..dc4577a7247 100644 --- a/src/react/hooks/internal/useStrictModeSafeCleanupEffect.ts +++ b/src/react/hooks/internal/useStrictModeSafeCleanupEffect.ts @@ -1,9 +1,9 @@ -import { useEffect } from 'react'; +import * as React from 'react'; export function useStrictModeSafeCleanupEffect(cleanup: () => void) { let timeout: NodeJS.Timeout; - useEffect(() => { + React.useEffect(() => { clearTimeout(timeout); return () => { diff --git a/src/react/hooks/useApolloClient.ts b/src/react/hooks/useApolloClient.ts index b15c9147194..b232af082f9 100644 --- a/src/react/hooks/useApolloClient.ts +++ b/src/react/hooks/useApolloClient.ts @@ -1,12 +1,12 @@ import { invariant } from '../../utilities/globals/index.js'; -import { useContext } from 'react'; +import * as React from 'react'; import type { ApolloClient } from '../../core/index.js'; import { getApolloContext } from '../context/index.js'; export function useApolloClient( override?: ApolloClient, ): ApolloClient { - const context = useContext(getApolloContext()); + const context = React.useContext(getApolloContext()); const client = override || context.client; invariant( !!client, diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts index cfa5b4a3b88..c46ff1a6c6f 100644 --- a/src/react/hooks/useBackgroundQuery.ts +++ b/src/react/hooks/useBackgroundQuery.ts @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback } from 'react'; +import * as React from 'react'; import type { DocumentNode, OperationVariables, @@ -138,7 +138,7 @@ export function useBackgroundQuery< client.watchQuery(watchQueryOptions) ); - const [promiseCache, setPromiseCache] = useState( + const [promiseCache, setPromiseCache] = React.useState( () => new Map([[queryRef.key, queryRef.promise]]) ); @@ -149,7 +149,7 @@ export function useBackgroundQuery< useTrackedQueryRefs(queryRef); - const fetchMore: FetchMoreFunction = useCallback( + const fetchMore: FetchMoreFunction = React.useCallback( (options) => { const promise = queryRef.fetchMore(options); @@ -162,7 +162,7 @@ export function useBackgroundQuery< [queryRef] ); - const refetch: RefetchFunction = useCallback( + const refetch: RefetchFunction = React.useCallback( (variables) => { const promise = queryRef.refetch(variables); @@ -177,7 +177,7 @@ export function useBackgroundQuery< queryRef.promiseCache = promiseCache; - return useMemo(() => { + return React.useMemo(() => { return [ { [QUERY_REFERENCE_SYMBOL]: queryRef }, { diff --git a/src/react/hooks/useFragment.ts b/src/react/hooks/useFragment.ts index 0f26ead89c8..aef7a50db8b 100644 --- a/src/react/hooks/useFragment.ts +++ b/src/react/hooks/useFragment.ts @@ -1,4 +1,4 @@ -import { useRef } from "react"; +import * as React from "react"; import { equal } from "@wry/equality"; import type { DeepPartial} from "../../utilities/index.js"; @@ -69,7 +69,7 @@ export function useFragment< optimistic }; - const resultRef = useRef>(); + const resultRef = React.useRef>(); let latestDiff = cache.diff(diffOptions); // Used for both getSnapshot and getServerSnapshot diff --git a/src/react/hooks/useLazyQuery.ts b/src/react/hooks/useLazyQuery.ts index 6452a5bccf2..700f05a7740 100644 --- a/src/react/hooks/useLazyQuery.ts +++ b/src/react/hooks/useLazyQuery.ts @@ -1,6 +1,6 @@ import type { DocumentNode } from 'graphql'; import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; -import { useCallback, useMemo, useRef } from 'react'; +import * as React from 'react'; import type { OperationVariables } from '../../core/index.js'; import { mergeOptions } from '../../utilities/index.js'; @@ -29,9 +29,9 @@ export function useLazyQuery, options?: LazyQueryHookOptions, NoInfer> ): LazyQueryResultTuple { - const execOptionsRef = useRef>>(); - const optionsRef = useRef>(); - const queryRef = useRef>(); + const execOptionsRef = React.useRef>>(); + const optionsRef = React.useRef>(); + const queryRef = React.useRef>(); const merged = mergeOptions(options, execOptionsRef.current || {}); const document = merged?.query ?? query; @@ -60,7 +60,7 @@ export function useLazyQuery { + const eagerMethods = React.useMemo(() => { const eagerMethods: Record = {}; for (const key of EAGER_METHODS) { const method = result[key]; @@ -79,7 +79,7 @@ export function useLazyQuery[0] >(executeOptions => { execOptionsRef.current = executeOptions ? { diff --git a/src/react/hooks/useMutation.ts b/src/react/hooks/useMutation.ts index 9932f70162b..813db9b5a7e 100644 --- a/src/react/hooks/useMutation.ts +++ b/src/react/hooks/useMutation.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; +import * as React from 'react'; import type { DocumentNode } from 'graphql'; import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import type { @@ -32,13 +32,13 @@ export function useMutation< ): MutationTuple { const client = useApolloClient(options?.client); verifyDocumentType(mutation, DocumentType.Mutation); - const [result, setResult] = useState>({ + const [result, setResult] = React.useState>({ called: false, loading: false, client, }); - const ref = useRef({ + const ref = React.useRef({ result, mutationId: 0, isMounted: true, @@ -53,7 +53,7 @@ export function useMutation< Object.assign(ref.current, { client, options, mutation }); } - const execute = useCallback(( + const execute = React.useCallback(( executeOptions: MutationFunctionOptions< TData, TVariables, @@ -140,13 +140,13 @@ export function useMutation< }); }, []); - const reset = useCallback(() => { + const reset = React.useCallback(() => { if (ref.current.isMounted) { setResult({ called: false, loading: false, client }); } }, []); - useEffect(() => { + React.useEffect(() => { ref.current.isMounted = true; return () => { diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index d723c4c1e69..d7b951764e7 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -1,12 +1,6 @@ import { invariant } from '../../utilities/globals/index.js'; -import { - useCallback, - useContext, - useMemo, - useRef, - useState, -} from 'react'; +import * as React from 'react'; import { useSyncExternalStore } from './useSyncExternalStore.js'; import { equal } from '@wry/equality'; @@ -59,7 +53,7 @@ export function useInternalState( client: ApolloClient, query: DocumentNode | TypedDocumentNode, ): InternalState { - const stateRef = useRef>(); + const stateRef = React.useRef>(); if ( !stateRef.current || client !== stateRef.current.client || @@ -75,7 +69,7 @@ export function useInternalState( // setTick function. Updating this state by calling state.forceUpdate is the // only way we trigger React component updates (no other useState calls within // the InternalState class). - const [_tick, setTick] = useState(0); + const [_tick, setTick] = React.useState(0); state.forceUpdate = () => { setTick(tick => tick + 1); }; @@ -159,14 +153,14 @@ class InternalState { // initialization, this.renderPromises is usually undefined (unless SSR is // happening), but that's fine as long as it has been initialized that way, // rather than left uninitialized. - this.renderPromises = useContext(getApolloContext()).renderPromises; + this.renderPromises = React.useContext(getApolloContext()).renderPromises; this.useOptions(options); const obsQuery = this.useObservableQuery(); const result = useSyncExternalStore( - useCallback(() => { + React.useCallback(() => { if (this.renderPromises) { return () => {}; } @@ -469,7 +463,7 @@ class InternalState { || this.observable // Reuse this.observable if possible (and not SSR) || this.client.watchQuery(this.getObsQueryOptions()); - this.obsQueryFields = useMemo(() => ({ + this.obsQueryFields = React.useMemo(() => ({ refetch: obsQuery.refetch.bind(obsQuery), reobserve: obsQuery.reobserve.bind(obsQuery), fetchMore: obsQuery.fetchMore.bind(obsQuery), diff --git a/src/react/hooks/useReactiveVar.ts b/src/react/hooks/useReactiveVar.ts index e012878400e..f92aba79f68 100644 --- a/src/react/hooks/useReactiveVar.ts +++ b/src/react/hooks/useReactiveVar.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import * as React from 'react'; import type { ReactiveVar } from '../../core/index.js'; export function useReactiveVar(rv: ReactiveVar): T { @@ -6,12 +6,12 @@ export function useReactiveVar(rv: ReactiveVar): T { // We don't actually care what useState thinks the value of the variable // is, so we take only the update function from the returned array. - const setValue = useState(value)[1]; + const setValue = React.useState(value)[1]; // We subscribe to variable updates on initial mount and when the value has // changed. This avoids a subtle bug in React.StrictMode where multiple // listeners are added, leading to inconsistent updates. - useEffect(() => { + React.useEffect(() => { const probablySameValue = rv(); if (value !== probablySameValue) { // If the value of rv has already changed, we don't need to listen for the diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index 64d78cdf9ad..4dba00b24fe 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -1,4 +1,4 @@ -import { useState, useMemo, useEffect } from "react"; +import * as React from "react"; import invariant from "ts-invariant"; import { NetworkStatus } from "../../core/index.js"; import { QUERY_REFERENCE_SYMBOL, type QueryReference } from "../cache/QueryReference.js"; @@ -6,7 +6,7 @@ import { __use } from "./internal/index.js"; import { toApolloError } from "./useSuspenseQuery.js"; export function useReadQuery(queryRef: QueryReference) { - const [, forceUpdate] = useState(0); + const [, forceUpdate] = React.useState(0); const internalQueryRef = queryRef[QUERY_REFERENCE_SYMBOL]; invariant( internalQueryRef.promiseCache, @@ -15,7 +15,7 @@ export function useReadQuery(queryRef: QueryReference) { 'Please ensure you are passing the `queryRef` returned from `useBackgroundQuery`.' ); - const skipResult = useMemo(() => { + const skipResult = React.useMemo(() => { const error = toApolloError(internalQueryRef.result); return { @@ -33,7 +33,7 @@ export function useReadQuery(queryRef: QueryReference) { internalQueryRef.promiseCache.set(internalQueryRef.key, promise); } - useEffect(() => { + React.useEffect(() => { return internalQueryRef.listen((promise) => { internalQueryRef.promiseCache!.set(internalQueryRef.key, promise); forceUpdate((prevState) => prevState + 1); @@ -45,7 +45,7 @@ export function useReadQuery(queryRef: QueryReference) { ? skipResult : __use(promise); - return useMemo(() => { + return React.useMemo(() => { return { data: result.data, networkStatus: result.networkStatus, diff --git a/src/react/hooks/useSubscription.ts b/src/react/hooks/useSubscription.ts index aaa87015723..100fec0f388 100644 --- a/src/react/hooks/useSubscription.ts +++ b/src/react/hooks/useSubscription.ts @@ -1,5 +1,5 @@ import { invariant } from '../../utilities/globals/index.js'; -import { useState, useRef, useEffect } from 'react'; +import * as React from 'react'; import type { DocumentNode } from 'graphql'; import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; import { equal } from '@wry/equality'; @@ -17,10 +17,10 @@ export function useSubscription, options?: SubscriptionHookOptions, NoInfer>, ) { - const hasIssuedDeprecationWarningRef = useRef(false); + const hasIssuedDeprecationWarningRef = React.useRef(false); const client = useApolloClient(options?.client); verifyDocumentType(subscription, DocumentType.Subscription); - const [result, setResult] = useState>({ + const [result, setResult] = React.useState>({ loading: !options?.skip, error: void 0, data: void 0, @@ -47,7 +47,7 @@ export function useSubscription { + const [observable, setObservable] = React.useState(() => { if (options?.skip) { return null; } @@ -60,15 +60,15 @@ export function useSubscription { + const canResetObservableRef = React.useRef(false); + React.useEffect(() => { return () => { canResetObservableRef.current = true; }; }, []); - const ref = useRef({ client, subscription, options }); - useEffect(() => { + const ref = React.useRef({ client, subscription, options }); + React.useEffect(() => { let shouldResubscribe = options?.shouldResubscribe; if (typeof shouldResubscribe === 'function') { shouldResubscribe = !!shouldResubscribe(options!); @@ -112,7 +112,7 @@ export function useSubscription { + React.useEffect(() => { if (!observable) { return; } diff --git a/src/react/hooks/useSuspenseCache.ts b/src/react/hooks/useSuspenseCache.ts index e622055f5bd..73903b2ce3e 100644 --- a/src/react/hooks/useSuspenseCache.ts +++ b/src/react/hooks/useSuspenseCache.ts @@ -1,10 +1,10 @@ -import { useContext } from 'react'; +import * as React from 'react'; import { getApolloContext } from '../context/index.js'; import { invariant } from '../../utilities/globals/index.js'; import type { SuspenseCache } from '../cache/index.js'; export function useSuspenseCache(override?: SuspenseCache) { - const context = useContext(getApolloContext()); + const context = React.useContext(getApolloContext()); const suspenseCache = override || context.suspenseCache; invariant( diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index d116c076a15..10654122a85 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -1,5 +1,5 @@ import { invariant } from '../../utilities/globals/index.js'; -import { useRef, useCallback, useMemo, useEffect, useState } from 'react'; +import * as React from 'react'; import type { ApolloClient, ApolloQueryResult, @@ -167,7 +167,7 @@ export function useSuspenseQuery< client.watchQuery(watchQueryOptions) ); - const [promiseCache, setPromiseCache] = useState( + const [promiseCache, setPromiseCache] = React.useState( () => new Map([[queryRef.key, queryRef.promise]]) ); @@ -185,7 +185,7 @@ export function useSuspenseQuery< useTrackedQueryRefs(queryRef); - useEffect(() => { + React.useEffect(() => { return queryRef.listen((promise) => { setPromiseCache((promiseCache) => new Map(promiseCache).set(queryRef.key, promise) @@ -193,7 +193,7 @@ export function useSuspenseQuery< }); }, [queryRef]); - const skipResult = useMemo(() => { + const skipResult = React.useMemo(() => { const error = toApolloError(queryRef.result); return { @@ -206,7 +206,7 @@ export function useSuspenseQuery< const result = fetchPolicy === 'standby' ? skipResult : __use(promise); - const fetchMore: FetchMoreFunction = useCallback( + const fetchMore: FetchMoreFunction = React.useCallback( (options) => { const promise = queryRef.fetchMore(options); @@ -219,7 +219,7 @@ export function useSuspenseQuery< [queryRef] ); - const refetch: RefetchFunction = useCallback( + const refetch: RefetchFunction = React.useCallback( (variables) => { const promise = queryRef.refetch(variables); @@ -233,12 +233,12 @@ export function useSuspenseQuery< ); const subscribeToMore: SubscribeToMoreFunction = - useCallback( + React.useCallback( (options) => queryRef.observable.subscribeToMore(options), [queryRef] ); - return useMemo(() => { + return React.useMemo(() => { return { client, data: result.data, @@ -294,7 +294,7 @@ export function toApolloError(result: ApolloQueryResult) { } export function useTrackedQueryRefs(queryRef: InternalQueryReference) { - const trackedQueryRefs = useRef(new Set()); + const trackedQueryRefs = React.useRef(new Set()); trackedQueryRefs.current.add(queryRef);