Repository: tannerlinsley/react-query
Pull request #1673 (2021-01-19 06:19:21)

Adding missing config property
From documentation below it seems, code example missing `mutationKey`

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/tannerlinsley/react-query/re8svz66j](https://vercel.com/tannerlinsley/react-query/re8svz66j)  
✅ Preview: *In Progress*



Patch:
diff --git a/README.md b/README.md
index 143934d1d..1ff558d1d 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ Enjoy this library? Try the entire [TanStack](https://tanstack.com)! [React Tabl
 
 ## Visit [react-query.tanstack.com](https://react-query.tanstack.com) for docs, guides, API and more!
 
+Still on **React Query v2**? No problem! Check out the v2 docs here: https://react-query-v2.tanstack.com/.
 ## Quick Features
 
 - Transport/protocol/backend agnostic data fetching (REST, GraphQL, promises, whatever!)
diff --git a/docs/package.json b/docs/package.json
index 6f66755d0..bfe893d99 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -20,7 +20,6 @@
     "@next/mdx": "^9.5.3",
     "@next/plugin-google-analytics": "^9.5.3",
     "@reactions/component": "^2.0.2",
-    "@visx/hierarchy": "^1.0.0",
     "@visx/responsive": "^1.3.0",
     "@zeit/fetch": "^6.0.0",
     "@zeit/react-jsx-parser": "^2.0.0",
diff --git a/docs/src/components/SponsorPack.js b/docs/src/components/SponsorPack.js
deleted file mode 100644
index 340ebfcd7..000000000
--- a/docs/src/components/SponsorPack.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import React from 'react'
-import { Pack, hierarchy } from '@visx/hierarchy'
-import { ParentSize } from '@visx/responsive'
-
-export default function SponsorPack({ sponsors, height }) {
-  const pack = React.useMemo(
-    () => ({
-      children: sponsors,
-      name: 'root',
-      radius: 0,
-      distance: 0,
-    }),
-    [sponsors]
-  )
-
-  const root = React.useMemo(
-    () =>
-      hierarchy(pack)
-        .sum(d => 100 + d.monthlyPriceInCents)
-        .sort(
-          (a, b) => b.data.monthlyPriceInCents - a.data.monthlyPriceInCents
-        ),
-    [pack]
-  )
-
-  return (
-    <ParentSize>
-      {({ width }) => {
-        return width < 10 ? null : (
-          <div
-            style={{
-              width,
-              height,
-              position: 'relative',
-            }}
-          >
-            <style
-              dangerouslySetInnerHTML={{
-                __html: `
-
-              .spon-link {
-                transition: all .2s ease;
-                transform: translate(-50%, -50%);
-              }
-
-              .spon-link:hover {
-                z-index: 10;
-                transform: translate(-50%, -50%) scale(1.1);
-              }
-
-              .spon-link:hover .spon-tooltip {
-                opacity: 1;
-              }
-            `,
-              }}
-            />
-            <Pack root={root} size={[width, height]} padding={width * 0.005}>
-              {packData => {
-                const circles = packData.descendants().slice(1) // skip first layer
-                return (
-                  <div>
-                    {[...circles].reverse().map((circle, i) => (
-                      <a
-                        key={`circle-${i}`}
-                        href={
-                          circle.data.linkUrl ||
-                          `https://github.com/${circle.data.login}`
-                        }
-                        className="spon-link absolute shadow-lg bg-white rounded-full z-0"
-                        style={{
-                          left: circle.x,
-                          top: circle.y,
-                          width: circle.r * 2,
-                          height: circle.r * 2,
-                        }}
-                      >
-                        <div
-                          key={`circle-${i}`}
-                          className="absolute bg-no-repeat bg-center bg-contain rounded-full"
-                          style={{
-                            position: 'absolute',
-                            left: '50%',
-                            top: '50%',
-                            transform: 'translate(-50%, -50%)',
-                            width: '90%',
-                            height: '90%',
-                            backgroundImage: `url(${
-                              circle.data.imageUrl ||
-                              `https://avatars0.githubusercontent.com/${circle.data.login}?v=3&s=200`
-                            })`,
-                          }}
-                        />
-                        {circle.data.name ? (
-                          <div
-                            className="spon-tooltip absolute -top-0 left-1/2
-                          text-sm
-                          bg-gray-900 text-white p-2 pointer-events-none
-                          transform -translate-x-1/2 -translate-y-full opacity-0
-                          shadow-xl rounded-lg"
-                          >
-                            <span className="whitespace-no-wrap">
-                              {circle.data.name}
-                            </span>
-                          </div>
-                        ) : null}
-                      </a>
-                    ))}
-                  </div>
-                )
-              }}
-            </Pack>
-          </div>
-        )
-      }}
-    </ParentSize>
-  )
-}
diff --git a/docs/src/pages/comparison.md b/docs/src/pages/comparison.md
index 34ca529ca..ce8c08c19 100644
--- a/docs/src/pages/comparison.md
+++ b/docs/src/pages/comparison.md
@@ -63,7 +63,7 @@ Feature/Capability Key:
 
 > **<sup>1</sup> Lagged Query Data** - React Query provides a way to continue to see an existing query's data while the next query loads (similar to the same UX that suspense will soon provide natively). This is extremely important when writing pagination UIs or infinite loading UIs where you do not want to show a hard loading state whenever a new query is requested. Other libraries do not have this capability and render a hard loading state for the new query (unless it has been prefetched), while the new query loads.
 
-> **<sup>2</sup> Render Optimization** - React Query has excellent rendering performance. It will only re-render your components when a query is updated. For example because it has new data, or to indicate it is fetching. React Query also batches updates together to make sure your application only re-renders once when multiple components are using the same query. If you are only interested in the `data` or `error` properties, you can reduce the number of renders even more by setting `notifyOnChangeProps` to `['data', 'error']`.
+> **<sup>2</sup> Render Optimization** - React Query has excellent rendering performance. It will only re-render your components when a query is updated. For example because it has new data, or to indicate it is fetching. React Query also batches updates together to make sure your application only re-renders once when multiple components are using the same query. If you are only interested in the `data` or `error` properties, you can reduce the number of renders even more by setting `notifyOnChangeProps` to `['data', 'error']`. Set `notifyOnChangeProps: 'tracked'` to automatically track which fields are accessed and only re-render if one of them changes.
 
 > **<sup>3</sup> Partial query matching** - Because React Query uses deterministic query key serialization, this allows you to manipulate variable groups of queries without having to know each individual query-key that you want to match, eg. you can refetch every query that starts with `todos` in its key, regardless of variables, or you can target specific queries with (or without) variables or nested properties, and even use a filter function to only match queries that pass your specific conditions.
 
diff --git a/docs/src/pages/guides/initial-query-data.md b/docs/src/pages/guides/initial-query-data.md
index 01d62899f..502f4a69d 100644
--- a/docs/src/pages/guides/initial-query-data.md
+++ b/docs/src/pages/guides/initial-query-data.md
@@ -8,8 +8,8 @@ There are many ways to supply initial data for a query to the cache before you n
 - Declaratively:
   - Provide `initialData` to a query to prepopulate its cache if empty
 - Imperatively:
-  - [Prefetch the data using `queryClient.prefetchQuery`](../prefetching)
-  - [Manually place the data into the cache using `queryClient.setQueryData`](../prefetching)
+  - [Prefetch the data using `queryClient.prefetchQuery`](./prefetching)
+  - [Manually place the data into the cache using `queryClient.setQueryData`](./prefetching)
 
 ## Using `initialData` to prepopulate a query
 
diff --git a/docs/src/pages/guides/queries.md b/docs/src/pages/guides/queries.md
index 0c4e7f592..1eb68e439 100644
--- a/docs/src/pages/guides/queries.md
+++ b/docs/src/pages/guides/queries.md
@@ -37,7 +37,7 @@ The `result` object contains a few very important states you'll need to be aware
 - `isSuccess` or `status === 'success'` - The query was successful and data is available
 - `isIdle` or `status === 'idle'` - The query is currently disabled (you'll learn more about this in a bit)
 
-Beyond those primary state, more information is available depending on the state the query:
+Beyond those primary states, more information is available depending on the state of the query:
 
 - `error` - If the query is in an `isError` state, the error is available via the `error` property.
 - `data` - If the query is in a `success` state, the data is available via the `data` property.
diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js
index ac6a1896e..01b0cecce 100644
--- a/docs/src/pages/index.js
+++ b/docs/src/pages/index.js
@@ -1,5 +1,5 @@
 import * as React from 'react'
-import axios from 'axios'
+import { ParentSize } from '@visx/responsive'
 import Head from 'next/head'
 import Link from 'next/link'
 
@@ -12,21 +12,6 @@ import { Nav } from '../components/Nav'
 import { Footer } from '../components/Footer'
 import { ClientsMarquee } from '../components/clients/ClientsMarquee'
 import { Seo } from '../components/Seo'
-import SponsorPack from '../components/SponsorPack'
-
-export const getStaticProps = async () => {
-  const {
-    data: { sponsors, tiers },
-  } = await axios.get('https://tanstack.com/api/github-sponsors')
-
-  return {
-    props: {
-      sponsors,
-      tiers,
-    },
-    revalidate: 60, // In seconds
-  }
-}
 
 const Home = ({ sponsors }) => {
   return (
@@ -85,6 +70,17 @@ const Home = ({ sponsors }) => {
                       </a>
                     </div>
                   </div>
+                  <div className="mt-5">
+                    <p>
+                      Still using v2? No problem!{' '}
+                      <a
+                        href="https://react-query-v2.tanstack.com/docs/overview"
+                        className="text-blue-600 font-semibold transition-colors duration-150 ease-out"
+                      >
+                        Find the v2 docs here.
+                      </a>
+                    </p>
+                  </div>
                 </div>
               </div>
             </div>
@@ -235,8 +231,22 @@ const Home = ({ sponsors }) => {
             <h3 className="text-center text-3xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl sm:leading-10 lg:leading-none mt-8">
               Sponsors
             </h3>
-            <div className="py-4 flex flex-wrap">
-              <SponsorPack sponsors={sponsors} height={700} />
+            <div className="py-4 flex flex-wrap max-w-screen-md mx-auto">
+              <ParentSize>
+                {({ width }) => {
+                  return (
+                    <iframe
+                      title="sponsors"
+                      src="https://tanstack.com/sponsors-embed"
+                      style={{
+                        width: width,
+                        height: width,
+                        overflow: 'hidden',
+                      }}
+                    />
+                  )
+                }}
+              </ParentSize>
             </div>
             <div className="text-center mb-8">
               <a
diff --git a/docs/src/pages/reference/InfiniteQueryObserver.md b/docs/src/pages/reference/InfiniteQueryObserver.md
index aa64f7f83..71d85f581 100644
--- a/docs/src/pages/reference/InfiniteQueryObserver.md
+++ b/docs/src/pages/reference/InfiniteQueryObserver.md
@@ -23,4 +23,4 @@ const unsubscribe = observer.subscribe(result => {
 
 **Options**
 
-The options for the `InfiniteQueryObserver` are exactly the same as those of [`useInfiniteQuery`](#useinfinitequery).
+The options for the `InfiniteQueryObserver` are exactly the same as those of [`useInfiniteQuery`](/reference/useInfiniteQuery).
diff --git a/docs/src/pages/reference/QueryClient.md b/docs/src/pages/reference/QueryClient.md
index b6847850a..5f86b4a9e 100644
--- a/docs/src/pages/reference/QueryClient.md
+++ b/docs/src/pages/reference/QueryClient.md
@@ -185,7 +185,7 @@ queryClient.setQueryData(queryKey, updater)
 
 **Options**
 
-- `queryKey: QueryKey` [Query Keys](./guides/query-keys)
+- `queryKey: QueryKey`: [Query Keys](../guides/query-keys)
 - `updater: unknown | (oldData: TData | undefined) => TData`
   - If non-function is passed, the data will be updated to this value
   - If a function is passed, it will receive the old data value and be expected to return a new one.
@@ -228,8 +228,8 @@ The `invalidateQueries` method can be used to invalidate and refetch single or m
 ```js
 await queryClient.invalidateQueries('posts', {
   exact,
-  refetchActive = true,
-  refetchInactive = false
+  refetchActive: true,
+  refetchInactive: false
 }, { throwOnError })
 ```
 
diff --git a/docs/src/pages/reference/QueryObserver.md b/docs/src/pages/reference/QueryObserver.md
index 70ccfba12..51813453d 100644
--- a/docs/src/pages/reference/QueryObserver.md
+++ b/docs/src/pages/reference/QueryObserver.md
@@ -18,4 +18,4 @@ const unsubscribe = observer.subscribe(result => {
 
 **Options**
 
-The options for the `QueryObserver` are exactly the same as those of [`useQuery`](#usequery).
+The options for the `QueryObserver` are exactly the same as those of [`useQuery`](/reference/useQuery).
diff --git a/docs/src/pages/reference/focusManager.md b/docs/src/pages/reference/focusManager.md
index 323b39134..7aaa00798 100644
--- a/docs/src/pages/reference/focusManager.md
+++ b/docs/src/pages/reference/focusManager.md
@@ -37,7 +37,7 @@ focusManager.setEventListener(handleFocus => {
 
 ## `focusManager.setFocused`
 
-`setFocsued` can be used to manually set the focus state. Set `undefined` to fallback to the default focus check.
+`setFocused` can be used to manually set the focus state. Set `undefined` to fallback to the default focus check.
 
 ```js
 import { focusManager } from 'react-query'
diff --git a/docs/src/pages/reference/useInfiniteQuery.md b/docs/src/pages/reference/useInfiniteQuery.md
index 62ee6ebd1..c59b69ff9 100644
--- a/docs/src/pages/reference/useInfiniteQuery.md
+++ b/docs/src/pages/reference/useInfiniteQuery.md
@@ -21,7 +21,7 @@ const {
 
 **Options**
 
-The options for `useInfiniteQuery` are identical to the [`useQuery` hook](#usequery) with the addition of the following:
+The options for `useInfiniteQuery` are identical to the [`useQuery` hook](/reference/useQuery) with the addition of the following:
 
 - `queryFn: (context: QueryFunctionContext) => Promise<TData>`
   - **Required, but only if no default query function has been defined**
@@ -41,7 +41,7 @@ The options for `useInfiniteQuery` are identical to the [`useQuery` hook](#usequ
 
 **Returns**
 
-The returned properties for `useInfiniteQuery` are identical to the [`useQuery` hook](#usequery), with the addition of the following:
+The returned properties for `useInfiniteQuery` are identical to the [`useQuery` hook](/reference/useQuery), with the addition of the following:
 
 - `data.pages: TData[]`
   - Array containing all pages.
diff --git a/docs/src/pages/reference/useMutation.md b/docs/src/pages/reference/useMutation.md
index 5448bce41..d182480c9 100644
--- a/docs/src/pages/reference/useMutation.md
+++ b/docs/src/pages/reference/useMutation.md
@@ -17,6 +17,7 @@ const {
   reset,
   status,
 } = useMutation(mutationFn, {
+  mutationKey,
   onError,
   onMutate,
   onSettled,
diff --git a/docs/src/pages/reference/useQueries.md b/docs/src/pages/reference/useQueries.md
index c83a1261b..f69e5fe3a 100644
--- a/docs/src/pages/reference/useQueries.md
+++ b/docs/src/pages/reference/useQueries.md
@@ -14,7 +14,7 @@ const results = useQueries([
 
 **Options**
 
-The `useQueries` hook accepts an array with query option objects identical to the [`useQuery` hook](#usequery).
+The `useQueries` hook accepts an array with query option objects identical to the [`useQuery` hook](/reference/useQuery).
 
 **Returns**
 
diff --git a/docs/src/pages/reference/useQuery.md b/docs/src/pages/reference/useQuery.md
index d8a1bc474..9cb8be065 100644
--- a/docs/src/pages/reference/useQuery.md
+++ b/docs/src/pages/reference/useQuery.md
@@ -39,7 +39,7 @@ const {
   refetchOnWindowFocus,
   retry,
   retryDelay,
-  select
+  select,
   staleTime,
   structuralSharing,
   suspense,
@@ -109,10 +109,11 @@ const result = useQuery({
   - If set to `true`, the query will refetch on reconnect if the data is stale.
   - If set to `false`, the query will not refetch on reconnect.
   - If set to `"always"`, the query will always refetch on reconnect.
-- `notifyOnChangeProps: string[]`
+- `notifyOnChangeProps: string[] | "tracked"`
   - Optional
   - If set, the component will only re-render if any of the listed properties change.
   - If set to `['data', 'error']` for example, the component will only re-render when the `data` or `error` properties change.
+  - If set to `"tracked"`, access to properties will be tracked, and the component will only re-render when one of the tracked properties change.
 - `notifyOnChangePropsExclusions: string[]`
   - Optional
   - If set, the component will not re-render if any of the listed properties change.
diff --git a/docs/yarn.lock b/docs/yarn.lock
index 0704614dd..8e74e488a 100644
--- a/docs/yarn.lock
+++ b/docs/yarn.lock
@@ -1742,21 +1742,11 @@
   resolved "https://registry.yarnpkg.com/@types/async-retry/-/async-retry-1.2.1.tgz#fa9ac165907a8ee78f4924f4e393b656c65b5bb4"
   integrity sha512-yMQ6CVgICWtyFNBqJT3zqOc+TnqqEPLo4nKJNPFwcialiylil38Ie6q1ENeFTjvaLOkVim9K5LisHgAKJWidGQ==
 
-"@types/classnames@^2.2.9":
-  version "2.2.11"
-  resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.11.tgz#2521cc86f69d15c5b90664e4829d84566052c1cf"
-  integrity sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==
-
 "@types/color-name@^1.1.1":
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
   integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
 
-"@types/d3-hierarchy@^1.1.6":
-  version "1.1.7"
-  resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-1.1.7.tgz#14a57b0539f8929015f8ad96490de50a16211040"
-  integrity sha512-fvht6DOYKzqmXjMb/+xfgkmrWM4SD7rMA/ZbM+gGwr9ZTuIDfky95J8CARtaJo/ExeWyS0xGVdL2gqno2zrQ0Q==
-
 "@types/glob@^7.1.1":
   version "7.1.2"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987"
@@ -1778,9 +1768,9 @@
   integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
 
 "@types/lodash@^4.14.146":
-  version "4.14.167"
-  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.167.tgz#ce7d78553e3c886d4ea643c37ec7edc20f16765e"
-  integrity sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw==
+  version "4.14.168"
+  resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008"
+  integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==
 
 "@types/lru-cache@4.1.1":
   version "4.1.1"
@@ -1837,29 +1827,6 @@
   resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
   integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
 
-"@visx/group@1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@visx/group/-/group-1.0.0.tgz#d47ac94abec1d191602a501a4fdf7455dfaad0be"
-  integrity sha512-2YlhGHTINUl7do046p/bkIYiD4xDv/sJ4JAaGrqFXwX68EJZ5Er/0gpZZ4nrADQlxB8/uyJvZzp1Q54ySfTMiA==
-  dependencies:
-    "@types/classnames" "^2.2.9"
-    "@types/react" "*"
-    classnames "^2.2.5"
-    prop-types "^15.6.2"
-
-"@visx/hierarchy@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@visx/hierarchy/-/hierarchy-1.0.0.tgz#730aa053121180dafe139ac5b093571baf3d26c6"
-  integrity sha512-kT8RBebsbuN6zn+LaBhQEG+xVKd5IJki3kn0fki4pPHbJTaSMdNYUJicPtsKnWyRqxAuNQfKdKaKJfam4iNTtA==
-  dependencies:
-    "@types/classnames" "^2.2.9"
-    "@types/d3-hierarchy" "^1.1.6"
-    "@types/react" "*"
-    "@visx/group" "1.0.0"
-    classnames "^2.2.5"
-    d3-hierarchy "^1.1.4"
-    prop-types "^15.6.1"
-
 "@visx/responsive@^1.3.0":
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/@visx/responsive/-/responsive-1.3.0.tgz#4df0c9d9e2f72ac36e4e1c895b4624626209cca3"
@@ -3122,7 +3089,7 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
-classnames@2.2.6, classnames@^2.2.5, classnames@^2.2.6:
+classnames@2.2.6, classnames@^2.2.6:
   version "2.2.6"
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
   integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@@ -3601,9 +3568,9 @@ cssnano-simple@1.2.0:
     postcss "^7.0.32"
 
 csstype@^3.0.2:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.5.tgz#7fdec6a28a67ae18647c51668a9ff95bb2fa7bb8"
-  integrity sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef"
+  integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==
 
 currently-unhandled@^0.4.1:
   version "0.4.1"
@@ -3626,11 +3593,6 @@ cyclist@^1.0.1:
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
   integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
 
-d3-hierarchy@^1.1.4:
-  version "1.1.9"
-  resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83"
-  integrity sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==
-
 d@1, d@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts
index e40d5cc76..986326aab 100644
--- a/src/core/queryObserver.ts
+++ b/src/core/queryObserver.ts
@@ -54,6 +54,8 @@ export class QueryObserver<
   private initialErrorUpdateCount: number
   private staleTimeoutId?: number
   private refetchIntervalId?: number
+  private trackedProps!: Array<keyof QueryObserverResult>
+  private trackedCurrentResult!: QueryObserverResult<TData, TError>
 
   constructor(
     client: QueryClient,
@@ -65,6 +67,7 @@ export class QueryObserver<
     this.options = options
     this.initialDataUpdateCount = 0
     this.initialErrorUpdateCount = 0
+    this.trackedProps = []
     this.bindMethods()
     this.setOptions(options)
   }
@@ -208,6 +211,10 @@ export class QueryObserver<
     return this.currentResult
   }
 
+  getTrackedCurrentResult(): QueryObserverResult<TData, TError> {
+    return this.trackedCurrentResult
+  }
+
   getNextResult(
     options?: ResultOptions
   ): Promise<QueryObserverResult<TData, TError>> {
@@ -449,11 +456,15 @@ export class QueryObserver<
     }
 
     const keys = Object.keys(result)
+    const includedProps =
+      notifyOnChangeProps === 'tracked'
+        ? this.trackedProps
+        : notifyOnChangeProps
 
     for (let i = 0; i < keys.length; i++) {
       const key = keys[i] as keyof QueryObserverResult
       const changed = prevResult[key] !== result[key]
-      const isIncluded = notifyOnChangeProps?.some(x => x === key)
+      const isIncluded = includedProps?.some(x => x === key)
       const isExcluded = notifyOnChangePropsExclusions?.some(x => x === key)
 
       if (changed) {
@@ -461,7 +472,11 @@ export class QueryObserver<
           continue
         }
 
-        if (!notifyOnChangeProps || isIncluded) {
+        if (
+          !notifyOnChangeProps ||
+          isIncluded ||
+          (notifyOnChangeProps === 'tracked' && this.trackedProps.length === 0)
+        ) {
           return true
         }
       }
@@ -479,6 +494,26 @@ export class QueryObserver<
     // Only update if something has changed
     if (!shallowEqualObjects(result, this.currentResult)) {
       this.currentResult = result
+
+      if (this.options.notifyOnChangeProps === 'tracked') {
+        const addTrackedProps = (prop: keyof QueryObserverResult) => {
+          if (!this.trackedProps.includes(prop)) {
+            this.trackedProps.push(prop)
+          }
+        }
+        this.trackedCurrentResult = {} as QueryObserverResult<TData, TError>
+
+        Object.keys(result).forEach(key => {
+          Object.defineProperty(this.trackedCurrentResult, key, {
+            configurable: false,
+            enumerable: true,
+            get() {
+              addTrackedProps(key as keyof QueryObserverResult)
+              return result[key as keyof QueryObserverResult]
+            },
+          })
+        })
+      }
     }
   }
 
diff --git a/src/core/types.ts b/src/core/types.ts
index 7ad1fd1e8..9cabdcc2a 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -136,8 +136,9 @@ export interface QueryObserverOptions<
   /**
    * If set, the component will only re-render if any of the listed properties change.
    * When set to `['data', 'error']`, the component will only re-render when the `data` or `error` properties change.
+   * When set to `tracked`, access to properties will be tracked, and the component will only re-render when one of the tracked properties change.
    */
-  notifyOnChangeProps?: Array<keyof InfiniteQueryObserverResult>
+  notifyOnChangeProps?: Array<keyof InfiniteQueryObserverResult> | 'tracked'
   /**
    * If set, the component will not re-render if any of the listed properties change.
    */
diff --git a/src/react/useBaseQuery.ts b/src/react/useBaseQuery.ts
index 05c7becdd..c75338b5a 100644
--- a/src/react/useBaseQuery.ts
+++ b/src/react/useBaseQuery.ts
@@ -79,5 +79,7 @@ export function useBaseQuery<TQueryFnData, TError, TData, TQueryData>(
     }
   }
 
-  return currentResult
+  return observer.options.notifyOnChangeProps === 'tracked'
+    ? observer.getTrackedCurrentResult()
+    : currentResult
 }


Test patch:
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index 67d21e6ef..472bd6166 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -739,6 +739,146 @@ describe('useQuery', () => {
     expect(states[1].dataUpdatedAt).not.toBe(states[2].dataUpdatedAt)
   })
 
+  it('should track properties and only re-render when a tracked property changes', async () => {
+    const key = queryKey()
+    const states: UseQueryResult<string>[] = []
+
+    function Page() {
+      const state = useQuery(key, () => 'test', {
+        notifyOnChangeProps: 'tracked',
+      })
+
+      states.push(state)
+
+      const { refetch, data } = state
+
+      React.useEffect(() => {
+        if (data) {
+          refetch()
+        }
+      }, [refetch, data])
+
+      return (
+        <div>
+          <h1>{data ?? null}</h1>
+        </div>
+      )
+    }
+
+    const rendered = renderWithClient(queryClient, <Page />)
+
+    await waitFor(() => rendered.getByText('test'))
+
+    expect(states.length).toBe(2)
+    expect(states[0]).toMatchObject({ data: undefined })
+    expect(states[1]).toMatchObject({ data: 'test' })
+  })
+
+  it('should not re-render if a tracked prop changes, but it was excluded', async () => {
+    const key = queryKey()
+    const states: UseQueryResult<string>[] = []
+
+    function Page() {
+      const state = useQuery(key, () => 'test', {
+        notifyOnChangeProps: 'tracked',
+        notifyOnChangePropsExclusions: ['data'],
+      })
+
+      states.push(state)
+
+      return (
+        <div>
+          <h1>{state.data ?? 'null'}</h1>
+        </div>
+      )
+    }
+
+    const rendered = renderWithClient(queryClient, <Page />)
+
+    await waitFor(() => rendered.getByText('null'))
+    expect(states.length).toBe(1)
+    expect(states[0]).toMatchObject({ data: undefined })
+
+    await queryClient.refetchQueries(key)
+    await waitFor(() => rendered.getByText('null'))
+    expect(states.length).toBe(1)
+    expect(states[0]).toMatchObject({ data: undefined })
+  })
+
+  it('should return the referentially same object if nothing changes between fetches', async () => {
+    const key = queryKey()
+    let renderCount = 0
+    const states: UseQueryResult<string>[] = []
+
+    function Page() {
+      const state = useQuery(key, () => 'test', {
+        notifyOnChangeProps: 'tracked',
+      })
+
+      states.push(state)
+
+      const { data } = state
+
+      React.useEffect(() => {
+        renderCount++
+      }, [state])
+
+      return (
+        <div>
+          <h1>{data ?? null}</h1>
+        </div>
+      )
+    }
+
+    const rendered = renderWithClient(queryClient, <Page />)
+
+    await waitFor(() => rendered.getByText('test'))
+    expect(renderCount).toBe(2)
+    expect(states.length).toBe(2)
+    expect(states[0]).toMatchObject({ data: undefined })
+    expect(states[1]).toMatchObject({ data: 'test' })
+
+    act(() => rendered.rerender(<Page />))
+    await waitFor(() => rendered.getByText('test'))
+    expect(renderCount).toBe(2)
+    expect(states.length).toBe(3)
+    expect(states[0]).toMatchObject({ data: undefined })
+    expect(states[1]).toMatchObject({ data: 'test' })
+    expect(states[2]).toMatchObject({ data: 'test' })
+  })
+
+  it('should always re-render if we are tracking props but not using any', async () => {
+    const key = queryKey()
+    let renderCount = 0
+    const states: UseQueryResult<string>[] = []
+
+    function Page() {
+      const state = useQuery(key, () => 'test', {
+        notifyOnChangeProps: 'tracked',
+      })
+
+      states.push(state)
+
+      React.useEffect(() => {
+        renderCount++
+      }, [state])
+
+      return (
+        <div>
+          <h1>hello</h1>
+        </div>
+      )
+    }
+
+    renderWithClient(queryClient, <Page />)
+
+    await waitFor(() => renderCount > 1)
+    expect(renderCount).toBe(2)
+    expect(states.length).toBe(2)
+    expect(states[0]).toMatchObject({ data: undefined })
+    expect(states[1]).toMatchObject({ data: 'test' })
+  })
+
   it('should be able to remove a query', async () => {
     const key = queryKey()
     const states: UseQueryResult<number>[] = []