├── .circleci └── config.yml ├── .eslintrc.cjs ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── renovate.json5 ├── .gitignore ├── CODEOWNERS ├── LICENSE ├── README.md ├── config-overrides.js ├── index.html ├── package-lock.json ├── package.json ├── public └── vite.svg ├── src ├── index.css ├── index.jsx ├── layout.jsx ├── link.js ├── schema.js └── subscriptions.jsx └── vite.config.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | secops: apollo/circleci-secops-orb@2.0.7 5 | 6 | workflows: 7 | security-scans: 8 | jobs: 9 | - secops/gitleaks: 10 | context: 11 | - platform-docker-ro 12 | - github-orb 13 | - secops-oidc 14 | git-base-revision: <<#pipeline.git.base_revision>><><> 15 | git-revision: << pipeline.git.revision >> 16 | - secops/semgrep: 17 | context: 18 | - secops-oidc 19 | - github-orb 20 | git-base-revision: <<#pipeline.git.base_revision>><><> 21 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "npm" 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "daily" 10 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | .DS_Store 5 | .env 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by the Apollo SecOps team 2 | # Please customize this file as needed prior to merging. 3 | 4 | * @apollographql/client-typescript 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apollo Client Issue Reproduction 2 | 3 | Welcome! If you are here then you were likely referred to this repo when reporting an error to [`apollographql/apollo-client`][1]. The core team is invested in making the best client for GraphQL possible, so when you hit an error it is important to the team that the error is resolved as soon as possible. 4 | 5 | Unfortunately, describing an error in GitHub is often not enough to truly understand the reported issue. By creating a small reproduction test case using this template repo the Apollo Client team will be able to identify and fix your error much faster then they could without. 6 | 7 | This repo was created with [`vite`][2] and is appropriate for reproductions of client-rendered React applications. To make changes in the GraphQL schema make sure to look at the `./src/index.jsx` file where we define a GraphQL schema using [GraphQL.js][5] which will run in the browser. 8 | 9 | | ☑️ Apollo Client User Survey | 10 | | :----- | 11 | | What do you like best about Apollo Client? What needs to be improved? Please tell us by taking a [one-minute survey](https://docs.google.com/forms/d/e/1FAIpQLSczNDXfJne3ZUOXjk9Ursm9JYvhTh1_nFTDfdq3XBAFWCzplQ/viewform?usp=pp_url&entry.1170701325=Apollo+Client&entry.204965213=Readme). Your responses will help us understand Apollo Client usage and allow us to serve you better. | 12 | 13 | ## Reproductions with other frameworks 14 | 15 | - **Next.js**: see our [`apollographql/next-apollo-example`][3] repository 16 | - **React Native**: see our [`apollographql/rn-apollo-client-testbed`][4] repository 17 | 18 | If you are not using React, a small reproduction case with your framework of choice would go a long way. 19 | 20 | [1]: https://github.com/apollographql/apollo-client 21 | [2]: https://vitejs.dev/ 22 | [3]: https://github.com/apollographql/next-apollo-example 23 | [4]: https://github.com/apollographql/rn-apollo-client-testbed 24 | [5]: http://graphql.org/graphql-js/ 25 | 26 | # Reproduction Creation Steps 27 | 28 | 1. Fork this repository to your GitHub account, or fork the [CodeSandbox](https://codesandbox.io/s/github/apollographql/react-apollo-error-template?file=/src/index.jsx) and skip steps 2-4. 29 | 2. By default, cloning this repostiory gives you an Apollo Client 3.0-based application. If you would like to start with a legacy Apollo Client 2.6 application, clone or checkout the `ac2` branch: 30 | ```sh 31 | git clone --branch ac2 git@github.com:apollographql/react-apollo-error-template.git 32 | # Or, after cloning the repository: 33 | git checkout -t origin/ac2 34 | ``` 35 | 3. After cloning, install all dependencies with `npm install`. 36 | 4. Start the development server with `npm start`. 37 | 5. Make the changes that will reproduce this error locally. 38 | 6. When ready, push your changes back to GitHub and let the [`apollo-client` team](https://github.com/apollographql/apollo-client#maintainers) know where they can be found. 39 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const { 2 | override, 3 | addWebpackPlugin, 4 | } = require("customize-cra"); 5 | const webpack = require("webpack"); 6 | const StatsPlugin = require("stats-webpack-plugin"); 7 | 8 | module.exports = override( 9 | addWebpackPlugin( 10 | new webpack.DefinePlugin({ 11 | __DEV__: process.env.NODE_ENV !== "production", 12 | }), 13 | ), 14 | addWebpackPlugin( 15 | new StatsPlugin("stats.json", { 16 | chunkModules: true, 17 | }), 18 | ), 19 | ); 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vite + React 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "apollo-client-error-template", 4 | "version": "1.0.0", 5 | "license": "MIT", 6 | "type": "module", 7 | "dependencies": { 8 | "@apollo/client": "3.11.4", 9 | "graphql": "16.8.1", 10 | "graphql-ws": "5.16.0", 11 | "react": "18.3.1", 12 | "react-dom": "18.3.0", 13 | "react-router-dom": "6.22.3" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "18.3.3", 17 | "@types/react-dom": "18.3.0", 18 | "@vitejs/plugin-react": "4.2.1", 19 | "eslint": "8.57.0", 20 | "eslint-plugin-react": "7.34.1", 21 | "eslint-plugin-react-hooks": "4.6.2", 22 | "eslint-plugin-react-refresh": "0.4.6", 23 | "vite": "5.3.4" 24 | }, 25 | "scripts": { 26 | "start": "vite", 27 | "build": "vite build", 28 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 29 | "preview": "vite preview" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | padding: 0.5rem; 4 | } 5 | 6 | h1 { 7 | margin-top: 0; 8 | } 9 | 10 | .add-person { 11 | display: flex; 12 | flex-direction: column; 13 | width: 16em; 14 | } 15 | 16 | .add-person input { 17 | height: 2.25em; 18 | margin: 1em 0; 19 | } 20 | 21 | .add-person button { 22 | height: 2.25em; 23 | cursor: pointer; 24 | } -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | /*** APP ***/ 2 | import React, { useState } from "react"; 3 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 4 | import { createRoot } from "react-dom/client"; 5 | import { 6 | ApolloClient, 7 | ApolloProvider, 8 | InMemoryCache, 9 | gql, 10 | useQuery, 11 | useMutation, 12 | } from "@apollo/client"; 13 | 14 | import { link } from "./link.js"; 15 | import { Subscriptions } from "./subscriptions.jsx"; 16 | import { Layout } from "./layout.jsx"; 17 | import "./index.css"; 18 | 19 | const ALL_PEOPLE = gql` 20 | query AllPeople { 21 | people { 22 | id 23 | name 24 | } 25 | } 26 | `; 27 | 28 | const ADD_PERSON = gql` 29 | mutation AddPerson($name: String) { 30 | addPerson(name: $name) { 31 | id 32 | name 33 | } 34 | } 35 | `; 36 | 37 | function App() { 38 | const [name, setName] = useState(""); 39 | const { loading, data } = useQuery(ALL_PEOPLE); 40 | 41 | const [addPerson] = useMutation(ADD_PERSON, { 42 | update: (cache, { data: { addPerson: addPersonData } }) => { 43 | const peopleResult = cache.readQuery({ query: ALL_PEOPLE }); 44 | 45 | cache.writeQuery({ 46 | query: ALL_PEOPLE, 47 | data: { 48 | ...peopleResult, 49 | people: [...peopleResult.people, addPersonData], 50 | }, 51 | }); 52 | }, 53 | }); 54 | 55 | return ( 56 |
57 |

Home

58 |
59 | 60 | setName(evt.target.value)} 65 | /> 66 | 74 |
75 |

Names

76 | {loading ? ( 77 |

Loading…

78 | ) : ( 79 | 84 | )} 85 |
86 | ); 87 | } 88 | 89 | const client = new ApolloClient({ 90 | cache: new InMemoryCache(), 91 | link, 92 | }); 93 | 94 | const container = document.getElementById("root"); 95 | const root = createRoot(container); 96 | 97 | root.render( 98 | 99 | 100 | 101 | }> 102 | } /> 103 | } /> 104 | 105 | 106 | 107 | 108 | ); 109 | -------------------------------------------------------------------------------- /src/layout.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet, Link } from "react-router-dom"; 2 | 3 | export function Layout() { 4 | return ( 5 |
6 |
7 |

Apollo Client Issue Reproduction

8 |

9 | This application can be used to demonstrate an error in Apollo Client. 10 |

11 | 12 | 24 |
25 |
26 | 27 |
28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/link.js: -------------------------------------------------------------------------------- 1 | /*** LINK ***/ 2 | import { graphql, print } from "graphql"; 3 | import { ApolloLink, Observable } from "@apollo/client"; 4 | import { createClient } from "graphql-ws"; 5 | import { GraphQLWsLink } from "@apollo/client/link/subscriptions"; 6 | import { schema } from "./schema.js"; 7 | 8 | function delay(wait) { 9 | return new Promise((resolve) => setTimeout(resolve, wait)); 10 | } 11 | 12 | const staticDataLink = new ApolloLink((operation) => { 13 | return new Observable(async (observer) => { 14 | const { query, operationName, variables } = operation; 15 | await delay(300); 16 | try { 17 | const result = await graphql({ 18 | schema, 19 | source: print(query), 20 | variableValues: variables, 21 | operationName, 22 | }); 23 | observer.next(result); 24 | observer.complete(); 25 | } catch (err) { 26 | observer.error(err); 27 | } 28 | }); 29 | }); 30 | 31 | const url = "wss://uifesi.sse.codesandbox.io/graphql"; 32 | 33 | const wsLink = new GraphQLWsLink( 34 | createClient({ 35 | url, 36 | }) 37 | ); 38 | 39 | const definitionIsSubscription = (d) => { 40 | return d.kind === "OperationDefinition" && d.operation === "subscription"; 41 | }; 42 | 43 | // Use directional composition in order to customize the terminating link 44 | // based on operation type: a WebSocket for subscriptions and our own 45 | // custom ApolloLink for everything else. 46 | // For more information, see: https://www.apollographql.com/docs/react/api/link/introduction/#directional-composition 47 | export const link = ApolloLink.split( 48 | (operation) => operation.query.definitions.some(definitionIsSubscription), 49 | wsLink, 50 | staticDataLink 51 | ); 52 | -------------------------------------------------------------------------------- /src/schema.js: -------------------------------------------------------------------------------- 1 | /*** SCHEMA ***/ 2 | import { 3 | GraphQLSchema, 4 | GraphQLObjectType, 5 | GraphQLID, 6 | GraphQLString, 7 | GraphQLList, 8 | } from 'graphql'; 9 | 10 | const PersonType = new GraphQLObjectType({ 11 | name: 'Person', 12 | fields: { 13 | id: { type: GraphQLID }, 14 | name: { type: GraphQLString }, 15 | }, 16 | }); 17 | 18 | const peopleData = [ 19 | { id: 1, name: 'John Smith' }, 20 | { id: 2, name: 'Sara Smith' }, 21 | { id: 3, name: 'Budd Deey' }, 22 | ]; 23 | 24 | const QueryType = new GraphQLObjectType({ 25 | name: 'Query', 26 | fields: { 27 | people: { 28 | type: new GraphQLList(PersonType), 29 | resolve: () => peopleData, 30 | }, 31 | }, 32 | }); 33 | 34 | const MutationType = new GraphQLObjectType({ 35 | name: 'Mutation', 36 | fields: { 37 | addPerson: { 38 | type: PersonType, 39 | args: { 40 | name: { type: GraphQLString }, 41 | }, 42 | resolve: function (_, { name }) { 43 | const person = { 44 | id: peopleData[peopleData.length - 1].id + 1, 45 | name, 46 | }; 47 | 48 | peopleData.push(person); 49 | return person; 50 | } 51 | }, 52 | }, 53 | }); 54 | 55 | export const schema = new GraphQLSchema({ query: QueryType, mutation: MutationType }); 56 | -------------------------------------------------------------------------------- /src/subscriptions.jsx: -------------------------------------------------------------------------------- 1 | import { gql, useSubscription } from "@apollo/client"; 2 | 3 | const query = gql` 4 | subscription { 5 | numberIncremented 6 | } 7 | `; 8 | 9 | export function Subscriptions() { 10 | const { data, error, loading } = useSubscription(query); 11 | if (loading) return

Loading...

; 12 | return ( 13 | <> 14 |

Subscriptions

15 | {error ? ( 16 | 17 | ) : ( 18 | <> 19 |

Response:

20 |

Score: {data?.numberIncremented}

21 | 22 | )} 23 | 24 | ); 25 | } 26 | 27 | function SubscriptionError() { 28 | return ( 29 | <> 30 |

Error :(

31 |

32 | The CodeSandbox serving our WebSocket API may be sleeping, please visit{" "} 33 | 34 | https://uifesi.sse.codesandbox.io/graphql 35 | {" "} 36 | to wake it up. 37 |

38 |

39 | Once you see the{" "} 40 | 44 | Apollo Studio Explorer 45 | {" "} 46 | IDE at the link above, you can refresh this page. 47 |

48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { 8 | port: 3000, 9 | }, 10 | }); 11 | --------------------------------------------------------------------------------