From a753fdbdd0c78ae0ece9579b7a34dfb488b9f6a6 Mon Sep 17 00:00:00 2001
From: Tadeusz Aleksandruk <t.aleksandruk@gmail.com>
Date: Wed, 15 Jun 2022 16:15:46 +0200
Subject: [PATCH 1/4] Adding sandbox with creds includes, but from my own
 account

---
 docs/rtk-query/usage/examples.mdx | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/docs/rtk-query/usage/examples.mdx b/docs/rtk-query/usage/examples.mdx
index 55674e43cf..0e18e790b0 100644
--- a/docs/rtk-query/usage/examples.mdx
+++ b/docs/rtk-query/usage/examples.mdx
@@ -76,6 +76,22 @@ The example has some intentionally wonky behavior... when editing the name of a
   sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
 ></iframe>
 
+## React with GraphQL with custom client
+
+<iframe
+  src="https://codesandbox.io/s/determined-shannon-iv2p5x"
+  style={{
+    width: '100%',
+    height: '800px',
+    border: 0,
+    borderRadius: '4px',
+    overflow: 'hidden',
+  }}
+  title="RTK Query GraphQL Example with custom client"
+  allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
+  sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
+></iframe>
+
 ## Authentication
 
 There are several ways to handle authentication with RTK Query. This is a very basic example of taking a JWT from a login mutation, then setting that in our store. We then use `prepareHeaders` to inject the authentication headers into every subsequent request.

From 23407b66786c7b70d514b3edbe314c2362c3908f Mon Sep 17 00:00:00 2001
From: AleksandrukTad <t.aleksandruk@gmail.com>
Date: Wed, 15 Jun 2022 16:23:59 +0200
Subject: [PATCH 2/4] fixing section name

---
 docs/rtk-query/usage/examples.mdx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/rtk-query/usage/examples.mdx b/docs/rtk-query/usage/examples.mdx
index 0e18e790b0..d2964e8958 100644
--- a/docs/rtk-query/usage/examples.mdx
+++ b/docs/rtk-query/usage/examples.mdx
@@ -76,7 +76,7 @@ The example has some intentionally wonky behavior... when editing the name of a
   sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
 ></iframe>
 
-## React with GraphQL with custom client
+## React GraphQL with custom client
 
 <iframe
   src="https://codesandbox.io/s/determined-shannon-iv2p5x"

From db3ae4e725520844b12da3093944ee80786c4afa Mon Sep 17 00:00:00 2001
From: Aleksandruk <e-tzau@gft.com>
Date: Mon, 13 Feb 2023 13:23:49 +0100
Subject: [PATCH 3/4] Moving example to examples folder - graphql-custom-client

---
 docs/rtk-query/usage/examples.mdx             |   2 +-
 .../query/react/graphql-custom-client/.env    |   3 +
 .../react/graphql-custom-client/package.json  |  55 +++
 .../graphql-custom-client/public/index.html   |  44 +++
 .../public/manifest.json                      |   8 +
 .../public/mockServiceWorker.js               | 323 ++++++++++++++++++
 .../react/graphql-custom-client/src/App.tsx   |  12 +
 .../src/app/services/posts.ts                 |  83 +++++
 .../src/features/posts/PostsManager.tsx       | 110 ++++++
 .../react/graphql-custom-client/src/index.tsx |  26 ++
 .../src/mocks/browser.ts                      |   4 +
 .../graphql-custom-client/src/mocks/db.ts     |  71 ++++
 .../src/react-app-env.d.ts                    |   1 +
 .../react/graphql-custom-client/tsconfig.json |  25 ++
 14 files changed, 766 insertions(+), 1 deletion(-)
 create mode 100644 examples/query/react/graphql-custom-client/.env
 create mode 100644 examples/query/react/graphql-custom-client/package.json
 create mode 100644 examples/query/react/graphql-custom-client/public/index.html
 create mode 100644 examples/query/react/graphql-custom-client/public/manifest.json
 create mode 100644 examples/query/react/graphql-custom-client/public/mockServiceWorker.js
 create mode 100644 examples/query/react/graphql-custom-client/src/App.tsx
 create mode 100644 examples/query/react/graphql-custom-client/src/app/services/posts.ts
 create mode 100644 examples/query/react/graphql-custom-client/src/features/posts/PostsManager.tsx
 create mode 100644 examples/query/react/graphql-custom-client/src/index.tsx
 create mode 100644 examples/query/react/graphql-custom-client/src/mocks/browser.ts
 create mode 100644 examples/query/react/graphql-custom-client/src/mocks/db.ts
 create mode 100644 examples/query/react/graphql-custom-client/src/react-app-env.d.ts
 create mode 100644 examples/query/react/graphql-custom-client/tsconfig.json

diff --git a/docs/rtk-query/usage/examples.mdx b/docs/rtk-query/usage/examples.mdx
index d2964e8958..867abd578b 100644
--- a/docs/rtk-query/usage/examples.mdx
+++ b/docs/rtk-query/usage/examples.mdx
@@ -79,7 +79,7 @@ The example has some intentionally wonky behavior... when editing the name of a
 ## React GraphQL with custom client
 
 <iframe
-  src="https://codesandbox.io/s/determined-shannon-iv2p5x"
+  src="https://codesandbox.io/embed/github/reduxjs/redux-toolkit/tree/master/examples/query/reactgraphql-custom-client?fontsize=12&hidenavigation=1&theme=dark&module=%2Fsrc%2Fapi.js&runonclick="
   style={{
     width: '100%',
     height: '800px',
diff --git a/examples/query/react/graphql-custom-client/.env b/examples/query/react/graphql-custom-client/.env
new file mode 100644
index 0000000000..e73ecc6cd4
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/.env
@@ -0,0 +1,3 @@
+SKIP_PREFLIGHT_CHECK=true
+# https://github.com/facebook/create-react-app/issues/11940
+DISABLE_ESLINT_PLUGIN=true
diff --git a/examples/query/react/graphql-custom-client/package.json b/examples/query/react/graphql-custom-client/package.json
new file mode 100644
index 0000000000..59d3e5f104
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/package.json
@@ -0,0 +1,55 @@
+{
+  "name": "@examples-query-react/graphql-custom-client",
+  "private": true,
+  "version": "1.0.0",
+  "description": "",
+  "keywords": [],
+  "main": "./src/index.tsx",
+  "dependencies": {
+    "@chakra-ui/react": "1.0.0",
+    "@emotion/react": "^11.4.0",
+    "@emotion/styled": "^11.3.0",
+    "@mswjs/data": "^0.3.0",
+    "@reduxjs/toolkit": "^1.6.0-rc.1",
+    "@rtk-query/graphql-request-base-query": "^2.0.0",
+    "faker": "^5.5.3",
+    "framer-motion": "^2.9.5",
+    "graphql": "^15.5.0",
+    "graphql-request": "^3.4.0",
+    "msw": "0.28.2",
+    "react": "^18.1.0",
+    "react-dom": "^18.1.0",
+    "react-icons": "3.11.0",
+    "react-redux": "^8.0.2",
+    "react-router-dom": "6.3.0",
+    "react-scripts": "5.0.1"
+  },
+  "devDependencies": {
+    "@types/faker": "^5.5.5",
+    "@types/react": "^18.0.5",
+    "@types/react-dom": "^18.0.5",
+    "typescript": "~4.2.4"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "prebuild": "rm -rf dist/"
+  },
+  "eslintConfig": {
+    "extends": [
+      "react-app"
+    ],
+    "rules": {
+      "react/react-in-jsx-scope": "off"
+    }
+  },
+  "browserslist": [
+    ">0.2%",
+    "not dead",
+    "not ie <= 11",
+    "not op_mini all"
+  ],
+  "msw": {
+    "workerDirectory": "public"
+  }
+}
diff --git a/examples/query/react/graphql-custom-client/public/index.html b/examples/query/react/graphql-custom-client/public/index.html
new file mode 100644
index 0000000000..09e975e218
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/public/index.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+    <!--
+      manifest.json provides metadata used when your web app is installed on a
+      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
+    -->
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <!--
+      Notice the use of %PUBLIC_URL% in the tags above.
+      It will be replaced with the URL of the `public` folder during the build.
+      Only files inside the `public` folder can be referenced from the HTML.
+
+      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+      work correctly both with client-side routing and a non-root public URL.
+      Learn how to configure a non-root public URL by running `npm run build`.
+    -->
+    <title>React App</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+  </body>
+</html>
+
diff --git a/examples/query/react/graphql-custom-client/public/manifest.json b/examples/query/react/graphql-custom-client/public/manifest.json
new file mode 100644
index 0000000000..11c6e14f7c
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/public/manifest.json
@@ -0,0 +1,8 @@
+{
+  "short_name": "RTK Query Pagination Example",
+  "name": "Pagination Demo",
+  "start_url": ".",
+  "display": "standalone",
+  "theme_color": "#000000",
+  "background_color": "#ffffff"
+}
diff --git a/examples/query/react/graphql-custom-client/public/mockServiceWorker.js b/examples/query/react/graphql-custom-client/public/mockServiceWorker.js
new file mode 100644
index 0000000000..fa1e5f5319
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/public/mockServiceWorker.js
@@ -0,0 +1,323 @@
+/* eslint-disable */
+/* tslint:disable */
+
+/**
+ * Mock Service Worker (0.30.1).
+ * @see https://github.com/mswjs/msw
+ * - Please do NOT modify this file.
+ * - Please do NOT serve this file on production.
+ */
+
+const INTEGRITY_CHECKSUM = '82ef9b96d8393b6da34527d1d6e19187'
+const bypassHeaderName = 'x-msw-bypass'
+const activeClientIds = new Set()
+
+self.addEventListener('install', function () {
+  return self.skipWaiting()
+})
+
+self.addEventListener('activate', async function (event) {
+  return self.clients.claim()
+})
+
+self.addEventListener('message', async function (event) {
+  const clientId = event.source.id
+
+  if (!clientId || !self.clients) {
+    return
+  }
+
+  const client = await self.clients.get(clientId)
+
+  if (!client) {
+    return
+  }
+
+  const allClients = await self.clients.matchAll()
+
+  switch (event.data) {
+    case 'KEEPALIVE_REQUEST': {
+      sendToClient(client, {
+        type: 'KEEPALIVE_RESPONSE',
+      })
+      break
+    }
+
+    case 'INTEGRITY_CHECK_REQUEST': {
+      sendToClient(client, {
+        type: 'INTEGRITY_CHECK_RESPONSE',
+        payload: INTEGRITY_CHECKSUM,
+      })
+      break
+    }
+
+    case 'MOCK_ACTIVATE': {
+      activeClientIds.add(clientId)
+
+      sendToClient(client, {
+        type: 'MOCKING_ENABLED',
+        payload: true,
+      })
+      break
+    }
+
+    case 'MOCK_DEACTIVATE': {
+      activeClientIds.delete(clientId)
+      break
+    }
+
+    case 'CLIENT_CLOSED': {
+      activeClientIds.delete(clientId)
+
+      const remainingClients = allClients.filter((client) => {
+        return client.id !== clientId
+      })
+
+      // Unregister itself when there are no more clients
+      if (remainingClients.length === 0) {
+        self.registration.unregister()
+      }
+
+      break
+    }
+  }
+})
+
+// Resolve the "master" client for the given event.
+// Client that issues a request doesn't necessarily equal the client
+// that registered the worker. It's with the latter the worker should
+// communicate with during the response resolving phase.
+async function resolveMasterClient(event) {
+  const client = await self.clients.get(event.clientId)
+
+  if (client.frameType === 'top-level') {
+    return client
+  }
+
+  const allClients = await self.clients.matchAll()
+
+  return allClients
+    .filter((client) => {
+      // Get only those clients that are currently visible.
+      return client.visibilityState === 'visible'
+    })
+    .find((client) => {
+      // Find the client ID that's recorded in the
+      // set of clients that have registered the worker.
+      return activeClientIds.has(client.id)
+    })
+}
+
+async function handleRequest(event, requestId) {
+  const client = await resolveMasterClient(event)
+  const response = await getResponse(event, client, requestId)
+
+  // Send back the response clone for the "response:*" life-cycle events.
+  // Ensure MSW is active and ready to handle the message, otherwise
+  // this message will pend indefinitely.
+  if (client && activeClientIds.has(client.id)) {
+    ;(async function () {
+      const clonedResponse = response.clone()
+      sendToClient(client, {
+        type: 'RESPONSE',
+        payload: {
+          requestId,
+          type: clonedResponse.type,
+          ok: clonedResponse.ok,
+          status: clonedResponse.status,
+          statusText: clonedResponse.statusText,
+          body:
+            clonedResponse.body === null ? null : await clonedResponse.text(),
+          headers: serializeHeaders(clonedResponse.headers),
+          redirected: clonedResponse.redirected,
+        },
+      })
+    })()
+  }
+
+  return response
+}
+
+async function getResponse(event, client, requestId) {
+  const { request } = event
+  const requestClone = request.clone()
+  const getOriginalResponse = () => fetch(requestClone)
+
+  // Bypass mocking when the request client is not active.
+  if (!client) {
+    return getOriginalResponse()
+  }
+
+  // Bypass initial page load requests (i.e. static assets).
+  // The absence of the immediate/parent client in the map of the active clients
+  // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
+  // and is not ready to handle requests.
+  if (!activeClientIds.has(client.id)) {
+    return await getOriginalResponse()
+  }
+
+  // Bypass requests with the explicit bypass header
+  if (requestClone.headers.get(bypassHeaderName) === 'true') {
+    const cleanRequestHeaders = serializeHeaders(requestClone.headers)
+
+    // Remove the bypass header to comply with the CORS preflight check.
+    delete cleanRequestHeaders[bypassHeaderName]
+
+    const originalRequest = new Request(requestClone, {
+      headers: new Headers(cleanRequestHeaders),
+    })
+
+    return fetch(originalRequest)
+  }
+
+  // Send the request to the client-side MSW.
+  const reqHeaders = serializeHeaders(request.headers)
+  const body = await request.text()
+
+  const clientMessage = await sendToClient(client, {
+    type: 'REQUEST',
+    payload: {
+      id: requestId,
+      url: request.url,
+      method: request.method,
+      headers: reqHeaders,
+      cache: request.cache,
+      mode: request.mode,
+      credentials: request.credentials,
+      destination: request.destination,
+      integrity: request.integrity,
+      redirect: request.redirect,
+      referrer: request.referrer,
+      referrerPolicy: request.referrerPolicy,
+      body,
+      bodyUsed: request.bodyUsed,
+      keepalive: request.keepalive,
+    },
+  })
+
+  switch (clientMessage.type) {
+    case 'MOCK_SUCCESS': {
+      return delayPromise(
+        () => respondWithMock(clientMessage),
+        clientMessage.payload.delay,
+      )
+    }
+
+    case 'MOCK_NOT_FOUND': {
+      return getOriginalResponse()
+    }
+
+    case 'NETWORK_ERROR': {
+      const { name, message } = clientMessage.payload
+      const networkError = new Error(message)
+      networkError.name = name
+
+      // Rejecting a request Promise emulates a network error.
+      throw networkError
+    }
+
+    case 'INTERNAL_ERROR': {
+      const parsedBody = JSON.parse(clientMessage.payload.body)
+
+      console.error(
+        `\
+[MSW] Request handler function for "%s %s" has thrown the following exception:
+
+${parsedBody.errorType}: ${parsedBody.message}
+(see more detailed error stack trace in the mocked response body)
+
+This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error.
+If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
+`,
+        request.method,
+        request.url,
+      )
+
+      return respondWithMock(clientMessage)
+    }
+  }
+
+  return getOriginalResponse()
+}
+
+self.addEventListener('fetch', function (event) {
+  const { request } = event
+
+  // Bypass navigation requests.
+  if (request.mode === 'navigate') {
+    return
+  }
+
+  // Opening the DevTools triggers the "only-if-cached" request
+  // that cannot be handled by the worker. Bypass such requests.
+  if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
+    return
+  }
+
+  // Bypass all requests when there are no active clients.
+  // Prevents the self-unregistered worked from handling requests
+  // after it's been deleted (still remains active until the next reload).
+  if (activeClientIds.size === 0) {
+    return
+  }
+
+  const requestId = uuidv4()
+
+  return event.respondWith(
+    handleRequest(event, requestId).catch((error) => {
+      console.error(
+        '[MSW] Failed to mock a "%s" request to "%s": %s',
+        request.method,
+        request.url,
+        error,
+      )
+    }),
+  )
+})
+
+function serializeHeaders(headers) {
+  const reqHeaders = {}
+  headers.forEach((value, name) => {
+    reqHeaders[name] = reqHeaders[name]
+      ? [].concat(reqHeaders[name]).concat(value)
+      : value
+  })
+  return reqHeaders
+}
+
+function sendToClient(client, message) {
+  return new Promise((resolve, reject) => {
+    const channel = new MessageChannel()
+
+    channel.port1.onmessage = (event) => {
+      if (event.data && event.data.error) {
+        return reject(event.data.error)
+      }
+
+      resolve(event.data)
+    }
+
+    client.postMessage(JSON.stringify(message), [channel.port2])
+  })
+}
+
+function delayPromise(cb, duration) {
+  return new Promise((resolve) => {
+    setTimeout(() => resolve(cb()), duration)
+  })
+}
+
+function respondWithMock(clientMessage) {
+  return new Response(clientMessage.payload.body, {
+    ...clientMessage.payload,
+    headers: clientMessage.payload.headers,
+  })
+}
+
+function uuidv4() {
+  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+    const r = (Math.random() * 16) | 0
+    const v = c == 'x' ? r : (r & 0x3) | 0x8
+    return v.toString(16)
+  })
+}
diff --git a/examples/query/react/graphql-custom-client/src/App.tsx b/examples/query/react/graphql-custom-client/src/App.tsx
new file mode 100644
index 0000000000..d2c53f79bc
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/src/App.tsx
@@ -0,0 +1,12 @@
+import { Route, Routes } from 'react-router-dom'
+import { PostsManager } from './features/posts/PostsManager'
+
+function App() {
+  return (
+    <Routes>
+      <Route path="/" element={<PostsManager />} />
+    </Routes>
+  )
+}
+
+export default App
diff --git a/examples/query/react/graphql-custom-client/src/app/services/posts.ts b/examples/query/react/graphql-custom-client/src/app/services/posts.ts
new file mode 100644
index 0000000000..f3e40f24f0
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/src/app/services/posts.ts
@@ -0,0 +1,83 @@
+import {  createApi } from '@reduxjs/toolkit/query/react'
+import { gql } from 'graphql-request'
+import {graphqlRequestBaseQuery} from '@rtk-query/graphql-request-base-query'
+import { GraphQLClient } from "graphql-request";
+
+export const postStatuses = ['draft', 'published', 'pending_review'] as const
+
+export interface Post {
+  id: string
+  title: string
+  author: string
+  content: string
+  status: typeof postStatuses[number]
+  created_at: string
+  updated_at: string
+}
+
+export interface Pagination {
+  page: number
+  per_page: number
+  total: number
+  total_pages: number
+}
+
+export interface GetPostsResponse extends Pagination {
+  data: {
+    posts: Post[]
+  }
+}
+
+interface PostResponse {
+  data: {
+    post: Post
+  }
+}
+
+const client = new GraphQLClient("/graphql", {
+  credentials: "include"
+});
+
+export const api = createApi({
+  baseQuery: graphqlRequestBaseQuery({
+    client,
+    url: '/graphql',
+  }),
+  endpoints: (builder) => ({
+    getPosts: builder.query<
+      GetPostsResponse,
+      { page?: number; per_page?: number }
+    >({
+      query: ({ page, per_page }) => ({
+        document: gql`
+          query GetPosts($page: Int = 1, $per_page: Int = 10) {
+            posts(page: $page, per_page: $per_page) {
+              id
+              title
+            }
+          }
+        `,
+        variables: {
+          page,
+          per_page,
+        },
+      }),
+    }),
+    getPost: builder.query<Post, string>({
+      query: (id) => ({
+        document: gql`
+        query GetPost($id: ID!) {
+          post(id: ${id}) {
+            id
+            title
+            body
+          }
+        }
+        `,
+      }),
+      transformResponse: (response: PostResponse) => response.data.post,
+    }),
+  }),
+})
+
+export const { useGetPostsQuery, useGetPostQuery } = api
diff --git a/examples/query/react/graphql-custom-client/src/features/posts/PostsManager.tsx b/examples/query/react/graphql-custom-client/src/features/posts/PostsManager.tsx
new file mode 100644
index 0000000000..09496ee6b8
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/src/features/posts/PostsManager.tsx
@@ -0,0 +1,110 @@
+import * as React from 'react'
+import {
+  Badge,
+  Box,
+  Button,
+  Divider,
+  Flex,
+  Heading,
+  HStack,
+  Icon,
+  List,
+  ListIcon,
+  ListItem,
+  Spacer,
+  Stat,
+  StatLabel,
+  StatNumber,
+} from '@chakra-ui/react'
+import { MdArrowBack, MdArrowForward, MdBook } from 'react-icons/md'
+import { Post, useGetPostsQuery } from '../../app/services/posts'
+
+const getColorForStatus = (status: Post['status']) => {
+  return status === 'draft'
+    ? 'gray'
+    : status === 'pending_review'
+    ? 'orange'
+    : 'green'
+}
+
+const PostList = () => {
+  const [page, setPage] = React.useState(1)
+  const { data: posts, isLoading, isFetching } = useGetPostsQuery({ page })
+
+  if (isLoading) {
+    return <div>Loading</div>
+  }
+
+  if (!posts?.data) {
+    return <div>No posts :(</div>
+  }
+
+  return (
+    <Box>
+      <HStack spacing="14px">
+        <Button
+          onClick={() => setPage((prev) => prev - 1)}
+          isLoading={isFetching}
+          disabled={page === 1}
+        >
+          <Icon as={MdArrowBack} />
+        </Button>
+        <Button
+          onClick={() => setPage((prev) => prev + 1)}
+          isLoading={isFetching}
+          disabled={page === posts.total_pages}
+        >
+          <Icon as={MdArrowForward} />
+        </Button>
+        <Box>{`${page} / ${posts.total_pages}`}</Box>
+      </HStack>
+      <List spacing={3} mt={6}>
+        {posts?.data.posts.map(({ id, title, status }) => (
+          <ListItem key={id}>
+            <ListIcon as={MdBook} color="green.500" /> {title}{' '}
+            <Badge
+              ml="1"
+              fontSize="0.8em"
+              colorScheme={getColorForStatus(status)}
+            >
+              {status}
+            </Badge>
+          </ListItem>
+        ))}
+      </List>
+    </Box>
+  )
+}
+
+export const PostsCountStat = () => {
+  const { data: posts } = useGetPostsQuery({})
+
+  return (
+    <Stat>
+      <StatLabel>Total Posts</StatLabel>
+      <StatNumber>{`${posts?.total || 'NA'}`}</StatNumber>
+    </Stat>
+  )
+}
+
+export const PostsManager = () => {
+  return (
+    <Box>
+      <Flex wrap="wrap" bg="#011627" p={4} color="white">
+        <Box>
+          <Heading size="xl">Manage Posts</Heading>
+        </Box>
+        <Spacer />
+        <Box>
+          <PostsCountStat />
+        </Box>
+      </Flex>
+      <Divider />
+      <Box p={4}>
+        <PostList />
+      </Box>
+    </Box>
+  )
+}
+
+export default PostsManager
diff --git a/examples/query/react/graphql-custom-client/src/index.tsx b/examples/query/react/graphql-custom-client/src/index.tsx
new file mode 100644
index 0000000000..a7429fe087
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/src/index.tsx
@@ -0,0 +1,26 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App'
+import { api } from './app/services/posts'
+import { ChakraProvider } from '@chakra-ui/react'
+
+import { BrowserRouter } from 'react-router-dom'
+import { worker } from './mocks/browser'
+import { ApiProvider } from '@reduxjs/toolkit/query/react'
+
+// Initialize the msw worker, wait for the service worker registration to resolve, then mount
+worker.start({ quiet: true }).then(() => {
+  return ReactDOM.createRoot(
+    document.getElementById('root') as HTMLElement
+  ).render(
+    <React.StrictMode>
+      <ApiProvider api={api}>
+        <ChakraProvider>
+          <BrowserRouter>
+            <App />
+          </BrowserRouter>
+        </ChakraProvider>
+      </ApiProvider>
+    </React.StrictMode>
+  )
+})
diff --git a/examples/query/react/graphql-custom-client/src/mocks/browser.ts b/examples/query/react/graphql-custom-client/src/mocks/browser.ts
new file mode 100644
index 0000000000..82d072ec49
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/src/mocks/browser.ts
@@ -0,0 +1,4 @@
+import { setupWorker } from 'msw'
+import { handlers } from './db'
+
+export const worker = setupWorker(...handlers)
diff --git a/examples/query/react/graphql-custom-client/src/mocks/db.ts b/examples/query/react/graphql-custom-client/src/mocks/db.ts
new file mode 100644
index 0000000000..0aa6a3c2a4
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/src/mocks/db.ts
@@ -0,0 +1,71 @@
+import { nanoid } from '@reduxjs/toolkit'
+import { factory, primaryKey } from '@mswjs/data'
+import faker from 'faker'
+import { graphql } from 'msw'
+import { postStatuses } from '../app/services/posts'
+import type { Pagination, Post } from '../app/services/posts'
+
+const db = factory({
+  post: {
+    id: primaryKey(String),
+    name: String,
+    title: String,
+    author: String,
+    content: String,
+    status: String,
+    created_at: String,
+    updated_at: String,
+  },
+})
+
+const getRandomStatus = () =>
+  postStatuses[Math.floor(Math.random() * postStatuses.length)]
+
+const createPostData = (): Post => {
+  const date = faker.date.past().toISOString()
+  return {
+    id: nanoid(),
+    title: faker.lorem.words(),
+    author: faker.name.findName(),
+    content: faker.lorem.paragraphs(),
+    status: getRandomStatus(),
+    created_at: date,
+    updated_at: date,
+  }
+}
+
+;[...new Array(50)].forEach((_) => db.post.create(createPostData()))
+
+type PaginationOptions = {
+  page: number; per_page: number
+}
+
+interface Posts extends Pagination {
+  data: {
+    posts: Post[]
+  }
+}
+
+export const handlers = [
+  graphql.query<Posts, PaginationOptions>('GetPosts', (req, res, ctx) => {
+    const { page = 1, per_page = 10 } = req.variables
+
+    const posts = db.post.findMany({
+      take: per_page,
+      skip: Math.max(per_page * (page - 1), 0)
+    })
+
+    return res(
+      ctx.data({
+        data: {
+          posts
+        } as { posts: Post[] },
+        per_page,
+        page,
+        total_pages: Math.ceil(db.post.count() / per_page),
+        total: db.post.count(),
+      })
+    )
+  }),
+  ...db.post.toHandlers('graphql'),
+] as const
diff --git a/examples/query/react/graphql-custom-client/src/react-app-env.d.ts b/examples/query/react/graphql-custom-client/src/react-app-env.d.ts
new file mode 100644
index 0000000000..6431bc5fc6
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/src/react-app-env.d.ts
@@ -0,0 +1 @@
+/// <reference types="react-scripts" />
diff --git a/examples/query/react/graphql-custom-client/tsconfig.json b/examples/query/react/graphql-custom-client/tsconfig.json
new file mode 100644
index 0000000000..5f488e8e73
--- /dev/null
+++ b/examples/query/react/graphql-custom-client/tsconfig.json
@@ -0,0 +1,25 @@
+{
+  "include": [
+    "./src/**/*"
+  ],
+  "compilerOptions": {
+    "strict": true,
+    "esModuleInterop": true,
+    "lib": [
+      "dom",
+      "es2015"
+    ],
+    "jsx": "react-jsx",
+    "target": "es5",
+    "allowJs": true,
+    "skipLibCheck": true,
+    "allowSyntheticDefaultImports": true,
+    "forceConsistentCasingInFileNames": true,
+    "noFallthroughCasesInSwitch": true,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true
+  }
+}

From 2777ccbc88dbd0c7f48d7fe81adb77bcf9a07e8d Mon Sep 17 00:00:00 2001
From: Aleksandruk <e-tzau@gft.com>
Date: Mon, 13 Feb 2023 13:51:34 +0100
Subject: [PATCH 4/4] updating yarn lock

---
 yarn.lock | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/yarn.lock b/yarn.lock
index 4fdbfb5d62..7f8b1fa129 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3605,6 +3605,34 @@ __metadata:
   languageName: unknown
   linkType: soft
 
+"@examples-query-react/graphql-custom-client@workspace:examples/query/react/graphql-custom-client":
+  version: 0.0.0-use.local
+  resolution: "@examples-query-react/graphql-custom-client@workspace:examples/query/react/graphql-custom-client"
+  dependencies:
+    "@chakra-ui/react": 1.0.0
+    "@emotion/react": ^11.4.0
+    "@emotion/styled": ^11.3.0
+    "@mswjs/data": ^0.3.0
+    "@reduxjs/toolkit": ^1.6.0-rc.1
+    "@rtk-query/graphql-request-base-query": ^2.0.0
+    "@types/faker": ^5.5.5
+    "@types/react": ^18.0.5
+    "@types/react-dom": ^18.0.5
+    faker: ^5.5.3
+    framer-motion: ^2.9.5
+    graphql: ^15.5.0
+    graphql-request: ^3.4.0
+    msw: 0.28.2
+    react: ^18.1.0
+    react-dom: ^18.1.0
+    react-icons: 3.11.0
+    react-redux: ^8.0.2
+    react-router-dom: 6.3.0
+    react-scripts: 5.0.1
+    typescript: ~4.2.4
+  languageName: unknown
+  linkType: soft
+
 "@examples-query-react/graphql@workspace:examples/query/react/graphql":
   version: 0.0.0-use.local
   resolution: "@examples-query-react/graphql@workspace:examples/query/react/graphql"