Repository: apollographql/apollo-client

Problem statement:
Apollo client requires react ?!?
Hi guys!

Thank you for the great project it opens up  many possibilities using graphql!


I am an early bird user from the moment that apollo introduced apollo-client!

I have created a package called `@rxdi/graphql-client ` which helps me to wrap all of the `apollo-client logic and initialize it inside a dependency injection called @rxdi.

So far so good but i decided to upgrade `apollo-client` to a new `3.*.*` version using `@apollo/client` package and i got a bundle error that says `Cannot resolve dependency 'react'`

```
  1 | import { invariant } from "../../utilities/globals/index.js";
> 2 | import * as React from 'react';
    |                       ^
  3 | import { getApolloContext } from "./ApolloContext.js";
  4 | export var ApolloProvider = function (_a) {
  5 |     var client = _a.client, children = _a.children;
```

So i have investigated a little bit and i found out that inside the barrel export ['index.ts'](https://github.com/apollographql/apollo-client/blob/main/src/index.ts) there is a line `export * from './react';` i was shocked to see this so i follow it up and i found this file https://github.com/apollographql/apollo-client/blob/main/src/react/context/ApolloProvider.tsx

1. So i am confused why react doesn't present as a dependency but it is required inside the code ?
2. Why even there is a code related to a `framework` which tight couples the `@apollo/client` to `react` ?
3. I now need to fork and remove all of this code in order to get the new version of `@apollo/client` does that sounds right ?
4. Does now `@apollo/client` can be called as `react` framework for graphql so i can skip next time when i want to install something which was framework agnostic ?

I am not a React user i have my own framework build using Web Components if you want you can check it out  using this starter https://github.com/rxdi/starter-client-side-lit-html

By the way this starter is using also `apollo-client` for interacting with Elon Musk Space X API

One way to mitigate this issue is to try to setup the `@apollo/client` not using the barrel export but instead a specific one by path. I have found that `@apollo/client/core` suits me well so wish me luck ! :)


Please don't get me wrong i like every framework but i decided to invest time and build my own stack and i am relying to a framework agnostic packages.

Cheers,
Kristiyan Tachev






Hints:
This is something we’re trying to fix, but will have to wait til a major version release, as it would be a breaking change. For now, you will have to continue importing the client from the `@apollo/client/core` entry point. At some point, we do hope to isolate the different entry points so that other framework users do not have to import or download the React layer.
This could be an interesting consideration for AC 4.0, So i'll add it to our milestone for consideration. Cc @bignimbus 
Just ran into this while trying to use Apollo with Solid.
Same here when using Apollo with Svelte.
It is very annoying to have a peerDependency to React. Importing from `@apollo/client/core` does not really solve the problem since that module is still living under the `@apollo/client` package which has a React dependency.
The correct way to solve this problem will be to separate the `@apollo/client/core` under a dedicated `@apollo/client-core` library React agnostic.
@andreasonny83 `react` is only an [optional peer dependency](https://github.com/apollographql/apollo-client/blob/0aecf3e6fbe4fb371f4596305dc2a1cf82022f14/package.json#L77C1-L82C7) - your bundler should not force you to install it, only install it correctly in your `node_modules` folder structure in a way that is discoverable from Apollo Client if you already have `react` in your dependencies.
@phryneas that is true, however the `peerDependenciesMeta` was only introduced by NPM v7 meaning that using older versions on NPM will result on an error when trying to consume a library with a dependency on Apollo Client.
Can I ask what is the reason for Apollo to keep the Core package under `@apollo/client` rather than exporting it to a separate npm package?
@andreasonny83 npm v7 shipped with node 15, which went End of Life 2 years ago. Node 16 (the oldest officially supported node version, which will receive support for one more month at this point) already ships with npm v8.  
In one month, the oldest officially supported node version still receiving security updates will be node 18, which ships with npm 9.5.  
I know things can't always be super up-to-date, but there has to be a limit how far back we can support things :/
Also, note that the two worst case scenarios here are that you either get a warning message that doesn't stop you from doing anything or install a package of a few kilobytes that will not make it into your bundle in the end.

> Can I ask what is the reason for Apollo to keep the Core package under @apollo/client rather than exporting it to a separate npm package?

I'm sorry, but that decision predates me. 
I would guess it was mainly to help users keep their packages up-to-date, since before `@apollo/client` you had to install about 10 different packages and had to keep all of them in sync, which was very cumbersome and lead to a lot of bugs and frustration.

Patch:
diff --git a/config/entryPoints.js b/config/entryPoints.js
--- a/config/entryPoints.js
+++ b/config/entryPoints.js
@@ -27,6 +27,7 @@ const entryPoints = [
   { dirs: ["testing"], extensions: [".js", ".jsx"] },
   { dirs: ["testing", "core"] },
   { dirs: ["testing", "experimental"] },
+  { dirs: ["testing", "react"] },
   { dirs: ["utilities"] },
   { dirs: ["utilities", "subscriptions", "relay"] },
   { dirs: ["utilities", "subscriptions", "urql"] },
diff --git a/src/index.ts b/src/index.ts
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,2 +1 @@
 export * from "./core/index.js";
-export * from "./react/index.js";


Test patch:
diff --git a/docs/source/api/react/testing.md b/docs/source/api/react/testing.md
--- a/docs/source/api/react/testing.md
+++ b/docs/source/api/react/testing.md
@@ -11,7 +11,7 @@ api_reference: true
 
 
 ```js
-import { MockedProvider } from "@apollo/client/testing";
+import { MockedProvider } from "@apollo/client/testing/react";
 ```
 
 The `MockedProvider` component is a mocked version of [`ApolloProvider`](./hooks/#the-apolloprovider-component) that doesn't send network requests to your API. Instead, it allows you to specify the exact response payload for a given GraphQL operation. This enables you to test your application's operations without communicating with a server.
diff --git a/docs/source/development-testing/testing.mdx b/docs/source/development-testing/testing.mdx
--- a/docs/source/development-testing/testing.mdx
+++ b/docs/source/development-testing/testing.mdx
@@ -21,7 +21,8 @@ Let's say we want to test the following `Dog` component, which executes a basic
 
 ```jsx title="dog.jsx"
 import React from "react";
-import { gql, useQuery } from "@apollo/client";
+import { gql } from "@apollo/client";
+import { useQuery } from "@apollo/client/react";
 
 // Make sure that both the query and the component are exported
 export const GET_DOG_QUERY = gql`
@@ -55,7 +56,7 @@ A basic rendering test for the component looks like this (minus mocked responses
 ```jsx title="dog.test.js"
 import "@testing-library/jest-dom";
 import { render, screen } from "@testing-library/react";
-import { MockedProvider } from "@apollo/client/testing";
+import { MockedProvider } from "@apollo/client/testing/react";
 import { GET_DOG_QUERY, Dog } from "./dog";
 
 const mocks = []; // We'll fill this in next
@@ -119,7 +120,7 @@ Combining our code above, we get the following complete test:
 ```jsx title="dog.test.js"
 import "@testing-library/jest-dom";
 import { render, screen } from "@testing-library/react";
-import { MockedProvider } from "@apollo/client/testing";
+import { MockedProvider } from "@apollo/client/testing/react";
 import { GET_DOG_QUERY, Dog } from "./dog";
 
 const mocks = [
@@ -317,7 +318,8 @@ The following `DeleteButton` component executes the `DELETE_DOG_MUTATION` to del
 
 ```jsx title="delete-dog.jsx"
 import React from "react";
-import { gql, useMutation } from "@apollo/client";
+import { gql } from "@apollo/client";
+import { useMutation } from "@apollo/client/react";
 
 export const DELETE_DOG_MUTATION = gql`
   mutation deleteDog($name: String!) {
@@ -350,7 +352,7 @@ We can test the initial rendering of this component just like we [tested our `Do
 import '@testing-library/jest-dom';
 import userEvent from '@testing-library/user-event';
 import { render, screen } from '@testing-library/react';
-import { MockedProvider } from "@apollo/client/testing";
+import { MockedProvider } from "@apollo/client/testing/react";
 import { DeleteButton, DELETE_DOG_MUTATION } from "./delete-dog";
 
 it("should render without error", () => {
diff --git a/scripts/memory/tests.js b/scripts/memory/tests.js
--- a/scripts/memory/tests.js
+++ b/scripts/memory/tests.js
@@ -59,10 +59,6 @@ describe("@apollo/client/apollo-client.cjs", () => {
     assert.strictEqual(typeof bundle.ApolloClient, "function");
     assert.strictEqual(typeof bundle.InMemoryCache, "function");
 
-    // TODO This will change in AC4 when we move all React exports to the
-    // @apollo/client/react entry point (see issue #8190).
-    assert.strictEqual(typeof bundle.ApolloProvider, "function");
-
     // The CommonJS bundles referred to by the "main" fields in the various
     // package.json files that we generate during `npm run build` are all
     // independent, non-overlapping bundles, but apollo-client.cjs is its own
diff --git a/src/__tests__/__snapshots__/exports.ts.snap b/src/__tests__/__snapshots__/exports.ts.snap
--- a/src/__tests__/__snapshots__/exports.ts.snap
+++ b/src/__tests__/__snapshots__/exports.ts.snap
@@ -4,13 +4,10 @@ exports[`exports of public entry points @apollo/client 1`] = `
 Array [
   "ApolloCache",
   "ApolloClient",
-  "ApolloConsumer",
   "ApolloError",
   "ApolloLink",
-  "ApolloProvider",
   "Cache",
   "DocumentTransform",
-  "DocumentType",
   "HttpLink",
   "InMemoryCache",
   "MissingFieldError",
@@ -20,7 +17,6 @@ Array [
   "checkFetcher",
   "concat",
   "createHttpLink",
-  "createQueryPreloader",
   "createSignalIfSupported",
   "defaultDataIdFromObject",
   "defaultPrinter",
@@ -33,7 +29,6 @@ Array [
   "from",
   "fromError",
   "fromPromise",
-  "getApolloContext",
   "gql",
   "isApolloError",
   "isNetworkRequestSettled",
@@ -41,10 +36,7 @@ Array [
   "makeReference",
   "makeVar",
   "mergeOptions",
-  "operationName",
   "parseAndCheckHttpResponse",
-  "parser",
-  "resetApolloContext",
   "resetCaches",
   "rewriteURIForGET",
   "selectHttpOptionsAndBody",
@@ -52,22 +44,9 @@ Array [
   "selectURI",
   "serializeFetchParameter",
   "setLogVerbosity",
-  "skipToken",
   "split",
   "throwServerError",
   "toPromise",
-  "useApolloClient",
-  "useBackgroundQuery",
-  "useFragment",
-  "useLazyQuery",
-  "useLoadableQuery",
-  "useMutation",
-  "useQuery",
-  "useQueryRefHandlers",
-  "useReactiveVar",
-  "useReadQuery",
-  "useSubscription",
-  "useSuspenseQuery",
 ]
 `;
 
@@ -358,7 +337,6 @@ exports[`exports of public entry points @apollo/client/testing 1`] = `
 Array [
   "MockLink",
   "MockSubscriptionLink",
-  "MockedProvider",
   "createMockClient",
   "mockObservableLink",
   "mockSingleLink",
@@ -392,6 +370,12 @@ Array [
 ]
 `;
 
+exports[`exports of public entry points @apollo/client/testing/react 1`] = `
+Array [
+  "MockedProvider",
+]
+`;
+
 exports[`exports of public entry points @apollo/client/utilities 1`] = `
 Array [
   "AutoCleanedStrongCache",
diff --git a/src/__tests__/exports.ts b/src/__tests__/exports.ts
--- a/src/__tests__/exports.ts
+++ b/src/__tests__/exports.ts
@@ -31,6 +31,7 @@ import * as reactSSR from "../react/ssr";
 import * as testing from "../testing";
 import * as testingCore from "../testing/core";
 import * as testingExperimental from "../testing/experimental";
+import * as testingReact from "../testing/react";
 import * as utilities from "../utilities";
 import * as utilitiesGlobals from "../utilities/globals";
 import * as urqlUtilities from "../utilities/subscriptions/urql";
@@ -77,6 +78,7 @@ describe("exports of public entry points", () => {
   check("@apollo/client/testing", testing);
   check("@apollo/client/testing/core", testingCore);
   check("@apollo/client/testing/experimental", testingExperimental);
+  check("@apollo/client/testing/react", testingReact);
   check("@apollo/client/utilities", utilities);
   check("@apollo/client/utilities/globals", utilitiesGlobals);
   check("@apollo/client/utilities/subscriptions/urql", urqlUtilities);
diff --git a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx
--- a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx
+++ b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx
@@ -23,7 +23,6 @@ import {
   MockLink,
   MockSubscriptionLink,
   mockSingleLink,
-  MockedProvider,
   wait,
 } from "../../../testing";
 import {
@@ -70,6 +69,7 @@ import {
   disableActEnvironment,
   useTrackRenders,
 } from "@testing-library/react-render-stream";
+import { MockedProvider } from "../../../testing/react";
 
 afterEach(() => {
   jest.useRealTimers();
diff --git a/src/react/hooks/__tests__/useFragment.test.tsx b/src/react/hooks/__tests__/useFragment.test.tsx
--- a/src/react/hooks/__tests__/useFragment.test.tsx
+++ b/src/react/hooks/__tests__/useFragment.test.tsx
@@ -14,7 +14,6 @@ import {
   UseFragmentResult,
   useFragment,
 } from "../useFragment";
-import { MockedProvider } from "../../../testing";
 import { ApolloProvider } from "../../context";
 import {
   InMemoryCache,
@@ -41,6 +40,7 @@ import {
   renderHookToSnapshotStream,
   useTrackRenders,
 } from "@testing-library/react-render-stream";
+import { MockedProvider } from "../../../testing/react";
 
 describe("useFragment", () => {
   it("is importable and callable", () => {
diff --git a/src/react/hooks/__tests__/useLazyQuery.test.tsx b/src/react/hooks/__tests__/useLazyQuery.test.tsx
--- a/src/react/hooks/__tests__/useLazyQuery.test.tsx
+++ b/src/react/hooks/__tests__/useLazyQuery.test.tsx
@@ -15,7 +15,6 @@ import {
 import { Observable } from "../../../utilities";
 import { ApolloProvider } from "../../../react";
 import {
-  MockedProvider,
   mockSingleLink,
   wait,
   tick,
@@ -31,6 +30,7 @@ import {
   disableActEnvironment,
   renderHookToSnapshotStream,
 } from "@testing-library/react-render-stream";
+import { MockedProvider } from "../../../testing/react";
 
 const IS_REACT_17 = React.version.startsWith("17");
 const IS_REACT_18 = React.version.startsWith("18");
diff --git a/src/react/hooks/__tests__/useLoadableQuery.test.tsx b/src/react/hooks/__tests__/useLoadableQuery.test.tsx
--- a/src/react/hooks/__tests__/useLoadableQuery.test.tsx
+++ b/src/react/hooks/__tests__/useLoadableQuery.test.tsx
@@ -19,8 +19,6 @@ import {
   split,
 } from "../../../core";
 import {
-  MockedProvider,
-  MockedProviderProps,
   MockedResponse,
   MockLink,
   MockSubscriptionLink,
@@ -60,6 +58,7 @@ import {
   useTrackRenders,
   AsyncRenderFn,
 } from "@testing-library/react-render-stream";
+import { MockedProvider, MockedProviderProps } from "../../../testing/react";
 const IS_REACT_19 = React.version.startsWith("19");
 
 afterEach(() => {
diff --git a/src/react/hooks/__tests__/useMutation.test.tsx b/src/react/hooks/__tests__/useMutation.test.tsx
--- a/src/react/hooks/__tests__/useMutation.test.tsx
+++ b/src/react/hooks/__tests__/useMutation.test.tsx
@@ -19,7 +19,6 @@ import {
 } from "../../../core";
 import { InMemoryCache } from "../../../cache";
 import {
-  MockedProvider,
   MockSubscriptionLink,
   mockSingleLink,
   MockedResponse,
@@ -40,6 +39,7 @@ import {
 } from "@testing-library/react-render-stream";
 import { MutationTuple, QueryResult } from "../../types/types";
 import { invariant } from "../../../utilities/globals";
+import { MockedProvider } from "../../../testing/react";
 
 describe("useMutation Hook", () => {
   interface Todo {
diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx
--- a/src/react/hooks/__tests__/useQuery.test.tsx
+++ b/src/react/hooks/__tests__/useQuery.test.tsx
@@ -21,7 +21,6 @@ import { Observable, Reference, concatPagination } from "../../../utilities";
 import { ApolloLink } from "../../../link/core";
 import {
   MockLink,
-  MockedProvider,
   MockSubscriptionLink,
   mockSingleLink,
   tick,
@@ -41,6 +40,7 @@ import {
   renderHookToSnapshotStream,
   disableActEnvironment,
 } from "@testing-library/react-render-stream";
+import { MockedProvider } from "../../../testing/react";
 
 const IS_REACT_17 = React.version.startsWith("17");
 const IS_REACT_18 = React.version.startsWith("18");
diff --git a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx
--- a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx
+++ b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx
@@ -38,7 +38,6 @@ import {
   offsetLimitPagination,
 } from "../../../utilities";
 import {
-  MockedProvider,
   MockedResponse,
   MockSubscriptionLink,
   MockLink,
@@ -66,6 +65,7 @@ import {
   disableActEnvironment,
   useTrackRenders,
 } from "@testing-library/react-render-stream";
+import { MockedProvider } from "../../../testing/react";
 
 const IS_REACT_19 = React.version.startsWith("19");
 
diff --git a/src/react/ssr/__tests__/useQuery.test.tsx b/src/react/ssr/__tests__/useQuery.test.tsx
--- a/src/react/ssr/__tests__/useQuery.test.tsx
+++ b/src/react/ssr/__tests__/useQuery.test.tsx
@@ -2,17 +2,14 @@
 import React from "react";
 import { DocumentNode } from "graphql";
 import gql from "graphql-tag";
-import {
-  MockedProvider,
-  MockedResponse,
-  mockSingleLink,
-} from "../../../testing";
+import { MockedResponse, mockSingleLink } from "../../../testing";
 import { ApolloClient } from "../../../core";
 import { InMemoryCache } from "../../../cache";
 import { ApolloProvider, getApolloContext } from "../../context";
 import { useApolloClient, useQuery } from "../../hooks";
 import { renderToStringWithData } from "..";
 import type { Trie } from "@wry/trie";
+import { MockedProvider } from "../../../testing/react";
 
 describe("useQuery Hook SSR", () => {
   const CAR_QUERY: DocumentNode = gql`
diff --git a/src/testing/index.ts b/src/testing/index.ts
--- a/src/testing/index.ts
+++ b/src/testing/index.ts
@@ -1,4 +1,2 @@
 import "../utilities/globals/index.js";
-export type { MockedProviderProps } from "./react/MockedProvider.js";
-export { MockedProvider } from "./react/MockedProvider.js";
 export * from "./core/index.js";
diff --git a/src/testing/react/index.ts b/src/testing/react/index.ts
new file mode 100644
--- /dev/null
+++ b/src/testing/react/index.ts
@@ -0,0 +1,2 @@
+export { MockedProvider } from "./MockedProvider.js";
+export type { MockedProviderProps } from "./MockedProvider.js";