Repository: tannerlinsley/react-query
Pull request #2619 (2021-08-31 04:28:20)

Allow handling some errors locally while using error boundaries
This adds a `shouldThrowError` option that can be used when `useErrorBoundary`/`suspense` to decide whether an error should be thrown or handled locally on the `error` state.

It's nice to have errors thrown to error boundaries. However sometimes you have some errors you do want to handle within the component instead of having them thrown to external boundaries.

e.g. In a product list page you may want to handle an "invalid product filter" from the server with an in-list error instead of it being thrown out to an error boundary. But you still may want to use an error boundary so 500 errors from the server and network errors still get thrown out to a shared error boundary.

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/21SjodwFvn173ySqWD8ZVW7zsY4H](https://vercel.com/tannerlinsley/react-query/21SjodwFvn173ySqWD8ZVW7zsY4H)  
✅ Preview: *In Progress*


codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/tannerlinsley/react-query/pr/2619/builds/164549) or the icon next to each commit SHA.

Patch:
diff --git a/docs/src/pages/comparison.md b/docs/src/pages/comparison.md
index 3b8a21fd5..4a3691ed4 100644
--- a/docs/src/pages/comparison.md
+++ b/docs/src/pages/comparison.md
@@ -8,56 +8,57 @@ title: Comparison | React Query vs SWR vs Apollo vs RTK Query
 Feature/Capability Key:
 
 - ✅ 1st-class, built-in, and ready to use with no added configuration or code
-- 🟡 Supported, but as an unoffical 3rd party or community library/contribution
+- 🟡 Supported, but as an unofficial 3rd party or community library/contribution
 - 🔶 Supported and documented, but requires extra user-code to implement
 - 🛑 Not officially supported or documented.
 
-|                                                    | React Query                              | SWR [_(Website)_][swr]     | Apollo Client [_(Website)_][apollo]   | RTK-Query [_(Website)_][rtk-query]   |
-| -------------------------------------------------- | ---------------------------------------- | -------------------------- | ------------------------------------- | ------------------------------------ |
-| Github Repo / Stars                                | [![][stars-react-query]][gh-react-query] | [![][stars-swr]][gh-swr]   | [![][stars-apollo]][gh-apollo]        | [![][stars-rtk-query]][gh-rtk-query] |
-| Platform Requirements                              | React                                    | React                      | React, GraphQL                        | Redux                                |
-| Their Comparison                                   |                                          | (none)                     | (none)                                | [Comparison][rtk-query-comparison]   |
-| Supported Query Syntax                             | Promise, REST, GraphQL                   | Promise, REST, GraphQL     | GraphQL                               | Promise, REST, GraphQL               |
-| Supported Frameworks                               | React                                    | React                      | React + Others                        | Any                                  |
-| Supported Query Keys                               | JSON                                     | JSON                       | GraphQL Query                         | JSON                                 |
-| Query Key Change Detection                         | Deep Compare (Stable Serialization)      | Referential Equality (===) | Deep Compare (Unstable Serialization) | Referential Equality (===)           |
-| Query Data Memoization Level                       | Query + Structural Sharing               | Query                      | Query + Entity + Structural Sharing   | Query                                |
-| Bundle Size                                        | [![][bp-react-query]][bpl-react-query]   | [![][bp-swr]][bpl-swr]     | [![][bp-apollo]][bpl-apollo]          | [![][bp-rtk-query]][bpl-rtk-query]   |
-| API Definition                                     | On-Use, Declarative                      | On-Use                     | GraphQL Schema                        | Declarative                          |
-| Queries                                            | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Caching                                            | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Devtools                                           | ✅                                       | 🟡                         | ✅                                    | ✅                                   |
-| Polling/Intervals                                  | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Parallel Queries                                   | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Dependent Queries                                  | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Paginated Queries                                  | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Infinite Queries                                   | ✅                                       | ✅                         | ✅                                    | 🛑                                   |
-| Bi-directional Infinite Queries                    | ✅                                       | 🔶                         | 🔶                                    | 🛑                                   |
-| Infinite Query Refetching                          | ✅                                       | ✅                         | 🛑                                    | 🛑                                   |
-| Lagged Query Data<sup>1</sup>                      | ✅                                       | 🛑                         | 🛑                                    | ✅                                   |
-| Selectors                                          | ✅                                       | 🛑                         | ✅                                    | ✅                                   |
-| Initial Data                                       | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Scroll Recovery                                    | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Cache Manipulation                                 | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Outdated Query Dismissal                           | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Render Optimization<sup>2</sup>                    | ✅                                       | 🛑                         | 🛑                                    | ✅                                   |
-| Auto Garbage Collection                            | ✅                                       | 🛑                         | 🛑                                    | ✅                                   |
-| Mutation Hooks                                     | ✅                                       | 🟡                         | ✅                                    | ✅                                   |
-| Offline Mutation Support                           | ✅                                       | 🛑                         | 🟡                                    | 🛑                                   |
-| Prefetching APIs                                   | ✅                                       | 🔶                         | ✅                                    | ✅                                   |
-| Query Cancellation                                 | ✅                                       | 🛑                         | 🛑                                    | 🛑                                   |
-| Partial Query Matching<sup>3</sup>                 | ✅                                       | 🛑                         | 🛑                                    | ✅                                   |
-| Stale While Revalidate                             | ✅                                       | ✅                         | ✅                                    | ✅                                   |
-| Stale Time Configuration                           | ✅                                       | 🛑                         | 🛑                                    | ✅                                   |
-| Pre-usage Query/Mutation Configuration<sup>4</sup> | ✅                                       | 🛑                         | 🛑                                    | ✅                                   |
-| Window Focus Refetching                            | ✅                                       | ✅                         | 🛑                                    | 🔶                                   |
-| Network Status Refetching                          | ✅                                       | ✅                         | ✅                                    | 🔶                                   |
-| General Cache Dehydration/Rehydration              | ✅                                       | 🛑                         | ✅                                    | ✅                                   |
-| Offline Caching                                    | ✅ (Experimental)                        | 🛑                         | ✅                                    | 🔶                                   |
-| React Suspense (Experimental)                      | ✅                                       | ✅                         | 🛑                                    | 🛑                                   |
-| Abstracted/Agnostic Core                           | ✅                                       | 🛑                         | ✅                                    | ✅                                   |
-| Automatic Refetch after Mutation<sup>5</sup>       | 🔶                                       | 🔶                         | ✅                                    | ✅                                   |
-| Normalized Caching<sup>6</sup>                     | 🛑                                       | 🛑                         | ✅                                    | 🛑                                   |
+|                                                    | React Query                              | SWR [_(Website)_][swr]      | Apollo Client [_(Website)_][apollo]   | RTK-Query [_(Website)_][rtk-query]   |
+| -------------------------------------------------- | ---------------------------------------- | --------------------------- | ------------------------------------- | ------------------------------------ |
+| Github Repo / Stars                                | [![][stars-react-query]][gh-react-query] | [![][stars-swr]][gh-swr]    | [![][stars-apollo]][gh-apollo]        | [![][stars-rtk-query]][gh-rtk-query] |
+| Platform Requirements                              | React                                    | React                       | React, GraphQL                        | Redux                                |
+| Their Comparison                                   |                                          | (none)                      | (none)                                | [Comparison][rtk-query-comparison]   |
+| Supported Query Syntax                             | Promise, REST, GraphQL                   | Promise, REST, GraphQL      | GraphQL                               | Promise, REST, GraphQL               |
+| Supported Frameworks                               | React                                    | React                       | React + Others                        | Any                                  |
+| Supported Query Keys                               | JSON                                     | JSON                        | GraphQL Query                         | JSON                                 |
+| Query Key Change Detection                         | Deep Compare (Stable Serialization)      | Shallow Compare             | Deep Compare (Unstable Serialization) | Referential Equality (===)           |
+| Query Data Change Detection                        | Deep Comparison + Structural Sharing     | Deep Compare (via `dequal`) | Deep Compare (Unstable Serialization) | Referential Equality (===)           |
+| Query Data Memoization Level                       | Query + Structural Sharing               | Query                       | Query + Entity + Structural Sharing   | Query                                |
+| Bundle Size                                        | [![][bp-react-query]][bpl-react-query]   | [![][bp-swr]][bpl-swr]      | [![][bp-apollo]][bpl-apollo]          | [![][bp-rtk-query]][bpl-rtk-query]   |
+| API Definition                                     | On-Use, Declarative                      | On-Use                      | GraphQL Schema                        | Declarative                          |
+| Queries                                            | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Caching                                            | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Devtools                                           | ✅                                       | 🟡                          | ✅                                    | ✅                                   |
+| Polling/Intervals                                  | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Parallel Queries                                   | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Dependent Queries                                  | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Paginated Queries                                  | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Infinite Queries                                   | ✅                                       | ✅                          | ✅                                    | 🛑                                   |
+| Bi-directional Infinite Queries                    | ✅                                       | 🔶                          | 🔶                                    | 🛑                                   |
+| Infinite Query Refetching                          | ✅                                       | ✅                          | 🛑                                    | 🛑                                   |
+| Lagged Query Data<sup>1</sup>                      | ✅                                       | 🔶                          | 🛑                                    | ✅                                   |
+| Selectors                                          | ✅                                       | 🛑                          | ✅                                    | ✅                                   |
+| Initial Data                                       | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Scroll Recovery                                    | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Cache Manipulation                                 | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Outdated Query Dismissal                           | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Render Batching & Optimization<sup>2</sup>         | ✅                                       | 🛑                          | 🛑                                    | ✅                                   |
+| Auto Garbage Collection                            | ✅                                       | 🛑                          | 🛑                                    | ✅                                   |
+| Mutation Hooks                                     | ✅                                       | 🟡                          | ✅                                    | ✅                                   |
+| Offline Mutation Support                           | ✅                                       | 🛑                          | 🟡                                    | 🛑                                   |
+| Prefetching APIs                                   | ✅                                       | 🔶                          | ✅                                    | ✅                                   |
+| Query Cancellation                                 | ✅                                       | 🛑                          | 🛑                                    | 🛑                                   |
+| Partial Query Matching<sup>3</sup>                 | ✅                                       | 🛑                          | 🛑                                    | ✅                                   |
+| Stale While Revalidate                             | ✅                                       | ✅                          | ✅                                    | ✅                                   |
+| Stale Time Configuration<sup>7</sup>               | ✅                                       | 🛑                          | 🛑                                    | ✅                                   |
+| Pre-usage Query/Mutation Configuration<sup>4</sup> | ✅                                       | 🛑                          | 🛑                                    | ✅                                   |
+| Window Focus Refetching                            | ✅                                       | ✅                          | 🛑                                    | 🔶                                   |
+| Network Status Refetching                          | ✅                                       | ✅                          | ✅                                    | 🔶                                   |
+| General Cache Dehydration/Rehydration              | ✅                                       | 🛑                          | ✅                                    | ✅                                   |
+| Offline Caching                                    | ✅ (Experimental)                        | 🛑                          | ✅                                    | 🔶                                   |
+| React Suspense (Experimental)                      | ✅                                       | ✅                          | 🛑                                    | 🛑                                   |
+| Abstracted/Agnostic Core                           | ✅                                       | 🛑                          | ✅                                    | ✅                                   |
+| Automatic Refetch after Mutation<sup>5</sup>       | 🔶                                       | 🔶                          | ✅                                    | ✅                                   |
+| Normalized Caching<sup>6</sup>                     | 🛑                                       | 🛑                          | ✅                                    | 🛑                                   |
 
 ### Notes
 
@@ -73,6 +74,8 @@ Feature/Capability Key:
 
 > **<sup>6</sup> Normalized Caching** - React Query, SWR and RTK-Query do not currently support automatic-normalized caching which describes storing entities in a flat architecture to avoid some high-level data duplication.
 
+> **<sup>6</sup> SWR's Immutable Mode** - SWR ships with an "immutable" mode that does allow you to only fetch a query once for the life of the cache, but it still does not have the concept of stale-time or conditional auto-revalidation
+
 <!-- -->
 
 [bpl-react-query]: https://bundlephobia.com/result?p=react-query
diff --git a/docs/src/pages/guides/suspense.md b/docs/src/pages/guides/suspense.md
index c4e3bf1fc..794a9c4a4 100644
--- a/docs/src/pages/guides/suspense.md
+++ b/docs/src/pages/guides/suspense.md
@@ -45,7 +45,7 @@ In addition to queries behaving differently in suspense mode, mutations also beh
 
 ## Resetting Error Boundaries
 
-Whether you are using **suspense** or **useErrorBoundaries** in your queries, you will need a way to let queries know that you want to try again when re-rendering after some error occured.
+Whether you are using **suspense** or **useErrorBoundaries** in your queries, you will need a way to let queries know that you want to try again when re-rendering after some error occurred.
 
 Query errors can be reset with the `QueryErrorResetBoundary` component or with the `useQueryErrorResetBoundary` hook.
 
diff --git a/docs/src/pages/reference/QueryErrorResetBoundary.md b/docs/src/pages/reference/QueryErrorResetBoundary.md
index 303e32be6..57f064836 100644
--- a/docs/src/pages/reference/QueryErrorResetBoundary.md
+++ b/docs/src/pages/reference/QueryErrorResetBoundary.md
@@ -3,7 +3,7 @@ id: QueryErrorResetBoundary
 title: QueryErrorResetBoundary
 ---
 
-When using **suspense** or **useErrorBoundaries** in your queries, you need a way to let queries know that you want to try again when re-rendering after some error occured. With the `QueryErrorResetBoundary` component you can reset any query errors within the boundaries of the component.
+When using **suspense** or **useErrorBoundaries** in your queries, you need a way to let queries know that you want to try again when re-rendering after some error occurred. With the `QueryErrorResetBoundary` component you can reset any query errors within the boundaries of the component.
 
 ```js
 import { QueryErrorResetBoundary } from 'react-query'
diff --git a/docs/src/pages/reference/useMutation.md b/docs/src/pages/reference/useMutation.md
index b1c9e9a74..9af167d1b 100644
--- a/docs/src/pages/reference/useMutation.md
+++ b/docs/src/pages/reference/useMutation.md
@@ -66,9 +66,11 @@ mutate(variables, {
   - This function receives a `retryAttempt` integer and the actual Error and returns the delay to apply before the next attempt in milliseconds.
   - A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff.
   - A function like `attempt => attempt * 1000` applies linear backoff.
-- `useErrorBoundary`
-  - Defaults to the global query config's `useErrorBoundary` value, which is `false`
-  - Set this to true if you want mutation errors to be thrown in the render phase and propagate to the nearest error boundary
+- `useErrorBoundary: undefined | boolean | (error: TError) => boolean`
+  - Defaults to the global query config's `useErrorBoundary` value, which is `undefined`
+  - Set this to `true` if you want mutation errors to be thrown in the render phase and propagate to the nearest error boundary
+  - Set this to `false` to disable the behaviour of throwing errors to the error boundary.
+  - If set to a function, it will be passed the error and should return a boolean indicating whether to show the error in an error boundary (`true`) or return the error as state (`false`)
 
 **Returns**
 
diff --git a/docs/src/pages/reference/useQuery.md b/docs/src/pages/reference/useQuery.md
index d62f8bd73..89d1e06a8 100644
--- a/docs/src/pages/reference/useQuery.md
+++ b/docs/src/pages/reference/useQuery.md
@@ -170,9 +170,11 @@ const result = useQuery({
   - Optional
   - Defaults to `true`
   - If set to `false`, structural sharing between query results will be disabled.
-- `useErrorBoundary: boolean`
-  - Defaults to the global query config's `useErrorBoundary` value, which is false
-  - Set this to true if you want errors to be thrown in the render phase and propagated to the nearest error boundary
+- `useErrorBoundary: undefined | boolean | (error: TError) => boolean`
+  - Defaults to the global query config's `useErrorBoundary` value, which is `undefined`
+  - Set this to `true` if you want errors to be thrown in the render phase and propagate to the nearest error boundary
+  - Set this to `false` to disable `suspense`'s default behaviour of throwing errors to the error boundary.
+  - If set to a function, it will be passed the error and should return a boolean indicating whether to show the error in an error boundary (`true`) or return the error as state (`false`)
 
 **Returns**
 
diff --git a/src/hydration/hydration.ts b/src/core/hydration.ts
similarity index 95%
rename from src/hydration/hydration.ts
rename to src/core/hydration.ts
index b0aa9bc9d..d10e1c83f 100644
--- a/src/hydration/hydration.ts
+++ b/src/core/hydration.ts
@@ -1,12 +1,12 @@
-import type { QueryClient } from '../core/queryClient'
-import type { Query, QueryState } from '../core/query'
+import type { QueryClient } from './queryClient'
+import type { Query, QueryState } from './query'
 import type {
   MutationKey,
   MutationOptions,
   QueryKey,
   QueryOptions,
-} from '../core/types'
-import type { Mutation, MutationState } from '../core/mutation'
+} from './types'
+import type { Mutation, MutationState } from './mutation'
 
 // TYPES
 
diff --git a/src/core/index.ts b/src/core/index.ts
index c3ecd0616..c09b2614d 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -12,8 +12,16 @@ export { focusManager } from './focusManager'
 export { onlineManager } from './onlineManager'
 export { hashQueryKey, isError } from './utils'
 export { isCancelledError } from './retryer'
+export { dehydrate, hydrate } from './hydration'
 
 // Types
 export * from './types'
 export type { Query } from './query'
 export type { Logger } from './logger'
+export type {
+  DehydrateOptions,
+  DehydratedState,
+  HydrateOptions,
+  ShouldDehydrateMutationFunction,
+  ShouldDehydrateQueryFunction,
+} from './hydration'
diff --git a/src/core/types.ts b/src/core/types.ts
index 106ca2787..3125d993b 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -163,9 +163,12 @@ export interface QueryObserverOptions<
   onSettled?: (data: TData | undefined, error: TError | null) => void
   /**
    * Whether errors should be thrown instead of setting the `error` property.
+   * If set to `true` or `suspense` is `true`, all errors will be thrown to the error boundary.
+   * If set to `false` and `suspense` is `false`, errors are returned as state.
+   * If set to a function, it will be passed the error and should return a boolean indicating whether to show the error in an error boundary (`true`) or return the error as state (`false`).
    * Defaults to `false`.
    */
-  useErrorBoundary?: boolean
+  useErrorBoundary?: boolean | ((error: TError) => boolean)
   /**
    * This option can be used to transform or select a part of the data returned by the query function.
    */
@@ -527,7 +530,7 @@ export interface MutationObserverOptions<
   TVariables = void,
   TContext = unknown
 > extends MutationOptions<TData, TError, TVariables, TContext> {
-  useErrorBoundary?: boolean
+  useErrorBoundary?: boolean | ((error: TError) => boolean)
 }
 
 export interface MutateOptions<
diff --git a/src/hydration/index.ts b/src/hydration/index.ts
index a52521402..1c8a35e43 100644
--- a/src/hydration/index.ts
+++ b/src/hydration/index.ts
@@ -1,11 +1,15 @@
-export { dehydrate, hydrate } from './hydration'
-export { useHydrate, Hydrate } from './react'
+// This package once contained these functions, but they have now been moved
+// into the core and react packages.
+// They are re-exported here to avoid a breaking change, but this package
+// should be considered deprecated and removed in a future major version.
+export { dehydrate, hydrate, useHydrate, Hydrate } from 'react-query'
 
 // Types
 export type {
   DehydrateOptions,
   DehydratedState,
   HydrateOptions,
+  ShouldDehydrateMutationFunction,
   ShouldDehydrateQueryFunction,
-} from './hydration'
-export type { HydrateProps } from './react'
+} from '../core/hydration'
+export type { HydrateProps } from '../react/Hydrate'
diff --git a/src/hydration/react.tsx b/src/react/Hydrate.tsx
similarity index 89%
rename from src/hydration/react.tsx
rename to src/react/Hydrate.tsx
index 62fe2964f..522189740 100644
--- a/src/hydration/react.tsx
+++ b/src/react/Hydrate.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 
-import { useQueryClient } from 'react-query'
-import { hydrate, HydrateOptions } from './hydration'
+import { hydrate, HydrateOptions } from '../core'
+import { useQueryClient } from './QueryClientProvider'
 
 export function useHydrate(state: unknown, options?: HydrateOptions) {
   const queryClient = useQueryClient()
diff --git a/src/react/index.ts b/src/react/index.ts
index 512d6d8e9..e8bcdee43 100644
--- a/src/react/index.ts
+++ b/src/react/index.ts
@@ -13,8 +13,10 @@ export { useMutation } from './useMutation'
 export { useQuery } from './useQuery'
 export { useQueries } from './useQueries'
 export { useInfiniteQuery } from './useInfiniteQuery'
+export { useHydrate, Hydrate } from './Hydrate'
 
 // Types
 export * from './types'
 export type { QueryClientProviderProps } from './QueryClientProvider'
 export type { QueryErrorResetBoundaryProps } from './QueryErrorResetBoundary'
+export type { HydrateProps } from './Hydrate'
diff --git a/src/react/types.ts b/src/react/types.ts
index ef98597c4..a077c7775 100644
--- a/src/react/types.ts
+++ b/src/react/types.ts
@@ -96,7 +96,7 @@ export interface UseMutationOptions<
   ) => Promise<unknown> | void
   retry?: RetryValue<TError>
   retryDelay?: RetryDelayValue<TError>
-  useErrorBoundary?: boolean
+  useErrorBoundary?: boolean | ((error: TError) => boolean)
 }
 
 export type UseMutateFunction<
diff --git a/src/react/useBaseQuery.ts b/src/react/useBaseQuery.ts
index 55e28c250..6db43cb2a 100644
--- a/src/react/useBaseQuery.ts
+++ b/src/react/useBaseQuery.ts
@@ -6,6 +6,7 @@ import { QueryObserver } from '../core/queryObserver'
 import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary'
 import { useQueryClient } from './QueryClientProvider'
 import { UseBaseQueryOptions } from './types'
+import { shouldThrowError } from './utils'
 
 export function useBaseQuery<
   TQueryFnData,
@@ -123,9 +124,13 @@ export function useBaseQuery<
 
   // Handle error boundary
   if (
-    (defaultedOptions.suspense || defaultedOptions.useErrorBoundary) &&
     result.isError &&
-    !result.isFetching
+    !result.isFetching &&
+    shouldThrowError(
+      defaultedOptions.suspense,
+      defaultedOptions.useErrorBoundary,
+      result.error
+    )
   ) {
     throw result.error
   }
diff --git a/src/react/useMutation.ts b/src/react/useMutation.ts
index a841b27d1..9262cefa9 100644
--- a/src/react/useMutation.ts
+++ b/src/react/useMutation.ts
@@ -10,6 +10,7 @@ import {
   UseMutationResult,
 } from './types'
 import { MutationFunction, MutationKey } from '../core/types'
+import { shouldThrowError } from './utils'
 
 // HOOK
 
@@ -102,7 +103,14 @@ export function useMutation<
     obsRef.current!.mutate(variables, mutateOptions).catch(noop)
   }, [])
 
-  if (currentResult.error && obsRef.current.options.useErrorBoundary) {
+  if (
+    currentResult.error &&
+    shouldThrowError(
+      undefined,
+      obsRef.current.options.useErrorBoundary,
+      currentResult.error
+    )
+  ) {
     throw currentResult.error
   }
 
diff --git a/src/react/utils.ts b/src/react/utils.ts
new file mode 100644
index 000000000..add40f1c8
--- /dev/null
+++ b/src/react/utils.ts
@@ -0,0 +1,16 @@
+export function shouldThrowError<TError>(
+  suspense: boolean | undefined,
+  _useErrorBoundary: boolean | ((err: TError) => boolean) | undefined,
+  error: TError
+): boolean {
+  // Allow useErrorBoundary function to override throwing behavior on a per-error basis
+  if (typeof _useErrorBoundary === 'function') {
+    return _useErrorBoundary(error)
+  }
+
+  // Allow useErrorBoundary to override suspense's throwing behaviour
+  if (typeof _useErrorBoundary === 'boolean') return _useErrorBoundary
+
+  // If suspense is enabled default to throwing errors
+  return !!suspense
+}


Test patch:
diff --git a/docs/src/pages/guides/testing.md b/docs/src/pages/guides/testing.md
index 9a68a7bae..470f1c0cf 100644
--- a/docs/src/pages/guides/testing.md
+++ b/docs/src/pages/guides/testing.md
@@ -70,7 +70,7 @@ This will set the defaults for all queries in the component tree to "no retries"
 
 ## Turn off network error logging
 
-When testing we want to supress network errors being logged to the console.
+When testing we want to suppress network errors being logged to the console.
 To do this, we can use `react-query`'s `setLogger()` function.
 
 ```ts
diff --git a/src/hydration/tests/hydration.test.tsx b/src/core/tests/hydration.test.tsx
similarity index 99%
rename from src/hydration/tests/hydration.test.tsx
rename to src/core/tests/hydration.test.tsx
index d8564dad6..4bc3bc208 100644
--- a/src/hydration/tests/hydration.test.tsx
+++ b/src/core/tests/hydration.test.tsx
@@ -1,5 +1,6 @@
 import { mockNavigatorOnLine, sleep } from '../../react/tests/utils'
-import { QueryCache, QueryClient } from '../..'
+import { QueryCache } from '../queryCache'
+import { QueryClient } from '../queryClient'
 import { dehydrate, hydrate } from '../hydration'
 
 async function fetchData<TData>(value: TData, ms?: number): Promise<TData> {
diff --git a/src/hydration/tests/react.test.tsx b/src/react/tests/Hydrate.test.tsx
similarity index 96%
rename from src/hydration/tests/react.test.tsx
rename to src/react/tests/Hydrate.test.tsx
index 70a371fa3..3a2b0b2f5 100644
--- a/src/hydration/tests/react.test.tsx
+++ b/src/react/tests/Hydrate.test.tsx
@@ -1,9 +1,16 @@
 import React from 'react'
 import { render } from '@testing-library/react'
 
-import { QueryClient, QueryClientProvider, QueryCache, useQuery } from '../..'
-import { dehydrate, useHydrate, Hydrate } from '../'
-import { sleep } from '../../react/tests/utils'
+import {
+  QueryClient,
+  QueryClientProvider,
+  QueryCache,
+  useQuery,
+  dehydrate,
+  useHydrate,
+  Hydrate,
+} from '../..'
+import { sleep } from './utils'
 
 describe('React hydration', () => {
   const fetchData: (value: string) => Promise<string> = value =>
diff --git a/src/hydration/tests/ssr.test.tsx b/src/react/tests/ssr-hydration.test.tsx
similarity index 97%
rename from src/hydration/tests/ssr.test.tsx
rename to src/react/tests/ssr-hydration.test.tsx
index 4a764d59b..f855b8635 100644
--- a/src/hydration/tests/ssr.test.tsx
+++ b/src/react/tests/ssr-hydration.test.tsx
@@ -2,10 +2,16 @@ import React from 'react'
 import ReactDOM from 'react-dom'
 import ReactDOMServer from 'react-dom/server'
 
-import { useQuery, QueryClient, QueryClientProvider, QueryCache } from '../..'
-import { dehydrate, hydrate } from '../'
+import {
+  useQuery,
+  QueryClient,
+  QueryClientProvider,
+  QueryCache,
+  dehydrate,
+  hydrate,
+} from '../..'
 import * as utils from '../../core/utils'
-import { mockConsoleError, sleep } from '../../react/tests/utils'
+import { mockConsoleError, sleep } from './utils'
 
 // This monkey-patches the isServer-value from utils,
 // so that we can pretend to be in a server environment
diff --git a/src/react/tests/suspense.test.tsx b/src/react/tests/suspense.test.tsx
index 7946cf3d6..1dd470411 100644
--- a/src/react/tests/suspense.test.tsx
+++ b/src/react/tests/suspense.test.tsx
@@ -550,6 +550,185 @@ describe("useQuery's in Suspense mode", () => {
     consoleMock.mockRestore()
   })
 
+  it('should throw errors to the error boundary by default', async () => {
+    const key = queryKey()
+
+    const consoleMock = mockConsoleError()
+
+    function Page() {
+      useQuery(
+        key,
+        async () => {
+          await sleep(10)
+          throw new Error('Suspense Error a1x')
+        },
+        {
+          retry: false,
+          suspense: true,
+        }
+      )
+      return <div>rendered</div>
+    }
+
+    function App() {
+      return (
+        <ErrorBoundary
+          fallbackRender={() => (
+            <div>
+              <div>error boundary</div>
+            </div>
+          )}
+        >
+          <React.Suspense fallback="Loading...">
+            <Page />
+          </React.Suspense>
+        </ErrorBoundary>
+      )
+    }
+
+    const rendered = renderWithClient(queryClient, <App />)
+
+    await waitFor(() => rendered.getByText('Loading...'))
+    await waitFor(() => rendered.getByText('error boundary'))
+
+    consoleMock.mockRestore()
+  })
+
+  it('should not throw errors to the error boundary when useErrorBoundary: false', async () => {
+    const key = queryKey()
+
+    const consoleMock = mockConsoleError()
+
+    function Page() {
+      useQuery(
+        key,
+        async () => {
+          await sleep(10)
+          throw new Error('Suspense Error a2x')
+        },
+        {
+          retry: false,
+          suspense: true,
+          useErrorBoundary: false,
+        }
+      )
+      return <div>rendered</div>
+    }
+
+    function App() {
+      return (
+        <ErrorBoundary
+          fallbackRender={() => (
+            <div>
+              <div>error boundary</div>
+            </div>
+          )}
+        >
+          <React.Suspense fallback="Loading...">
+            <Page />
+          </React.Suspense>
+        </ErrorBoundary>
+      )
+    }
+
+    const rendered = renderWithClient(queryClient, <App />)
+
+    await waitFor(() => rendered.getByText('Loading...'))
+    await waitFor(() => rendered.getByText('rendered'))
+
+    consoleMock.mockRestore()
+  })
+
+  it('should not throw errors to the error boundary when a useErrorBoundary function returns true', async () => {
+    const key = queryKey()
+
+    const consoleMock = mockConsoleError()
+
+    function Page() {
+      useQuery(
+        key,
+        async () => {
+          await sleep(10)
+          return Promise.reject('Remote Error')
+        },
+        {
+          retry: false,
+          suspense: true,
+          useErrorBoundary: err => err !== 'Local Error',
+        }
+      )
+      return <div>rendered</div>
+    }
+
+    function App() {
+      return (
+        <ErrorBoundary
+          fallbackRender={() => (
+            <div>
+              <div>error boundary</div>
+            </div>
+          )}
+        >
+          <React.Suspense fallback="Loading...">
+            <Page />
+          </React.Suspense>
+        </ErrorBoundary>
+      )
+    }
+
+    const rendered = renderWithClient(queryClient, <App />)
+
+    await waitFor(() => rendered.getByText('Loading...'))
+    await waitFor(() => rendered.getByText('error boundary'))
+
+    consoleMock.mockRestore()
+  })
+
+  it('should not throw errors to the error boundary when a useErrorBoundary function returns false', async () => {
+    const key = queryKey()
+
+    const consoleMock = mockConsoleError()
+
+    function Page() {
+      useQuery(
+        key,
+        async () => {
+          await sleep(10)
+          return Promise.reject('Local Error')
+        },
+        {
+          retry: false,
+          suspense: true,
+          useErrorBoundary: err => err !== 'Local Error',
+        }
+      )
+      return <div>rendered</div>
+    }
+
+    function App() {
+      return (
+        <ErrorBoundary
+          fallbackRender={() => (
+            <div>
+              <div>error boundary</div>
+            </div>
+          )}
+        >
+          <React.Suspense fallback="Loading...">
+            <Page />
+          </React.Suspense>
+        </ErrorBoundary>
+      )
+    }
+
+    const rendered = renderWithClient(queryClient, <App />)
+
+    await waitFor(() => rendered.getByText('Loading...'))
+    await waitFor(() => rendered.getByText('rendered'))
+
+    consoleMock.mockRestore()
+  })
+
   it('should not call the queryFn when not enabled', async () => {
     const key = queryKey()
 
diff --git a/src/react/tests/useQuery.test.tsx b/src/react/tests/useQuery.test.tsx
index b10ffd896..12def146e 100644
--- a/src/react/tests/useQuery.test.tsx
+++ b/src/react/tests/useQuery.test.tsx
@@ -19,6 +19,7 @@ import {
   QueryFunction,
   QueryFunctionContext,
 } from '../..'
+import { ErrorBoundary } from 'react-error-boundary'
 
 describe('useQuery', () => {
   const queryCache = new QueryCache()
@@ -2552,6 +2553,114 @@ describe('useQuery', () => {
     consoleMock.mockRestore()
   })
 
+  it('should throw error if queryFn throws and useErrorBoundary is in use', async () => {
+    const key = queryKey()
+    const consoleMock = mockConsoleError()
+
+    function Page() {
+      const { status, error } = useQuery<undefined, string>(
+        key,
+        () => Promise.reject('Error test jaylen'),
+        { retry: false, useErrorBoundary: true }
+      )
+
+      return (
+        <div>
+          <h1>{status}</h1>
+          <h2>{error}</h2>
+        </div>
+      )
+    }
+
+    const rendered = renderWithClient(
+      queryClient,
+      <ErrorBoundary fallbackRender={() => <div>error boundary</div>}>
+        <Page />
+      </ErrorBoundary>
+    )
+
+    await waitFor(() => rendered.getByText('error boundary'))
+
+    consoleMock.mockRestore()
+  })
+
+  it('should set status to error instead of throwing when error should not be thrown', async () => {
+    const key = queryKey()
+    const consoleMock = mockConsoleError()
+
+    function Page() {
+      const { status, error } = useQuery<undefined, string>(
+        key,
+        () => Promise.reject('Local Error'),
+        {
+          retry: false,
+          useErrorBoundary: err => err !== 'Local Error',
+        }
+      )
+
+      return (
+        <div>
+          <h1>{status}</h1>
+          <h2>{error}</h2>
+        </div>
+      )
+    }
+
+    const rendered = renderWithClient(
+      queryClient,
+      <ErrorBoundary fallbackRender={() => <div>error boundary</div>}>
+        <Page />
+      </ErrorBoundary>
+    )
+
+    await waitFor(() => rendered.getByText('error'))
+    await waitFor(() => rendered.getByText('Local Error'))
+
+    consoleMock.mockRestore()
+  })
+
+  it('should throw error instead of setting status when error should be thrown', async () => {
+    const key = queryKey()
+    const consoleMock = mockConsoleError()
+
+    function Page() {
+      const { status, error } = useQuery<undefined, string>(
+        key,
+        () => Promise.reject('Remote Error'),
+        {
+          retry: false,
+          useErrorBoundary: err => err !== 'Local Error',
+        }
+      )
+
+      return (
+        <div>
+          <h1>{status}</h1>
+          <h2>{error}</h2>
+        </div>
+      )
+    }
+
+    const rendered = renderWithClient(
+      queryClient,
+      <ErrorBoundary
+        fallbackRender={({ error }) => (
+          <div>
+            <div>error boundary</div>
+            <div>{error}</div>
+          </div>
+        )}
+      >
+        <Page />
+      </ErrorBoundary>
+    )
+
+    await waitFor(() => rendered.getByText('error boundary'))
+    await waitFor(() => rendered.getByText('Remote Error'))
+
+    consoleMock.mockRestore()
+  })
+
   it('should always fetch if refetchOnMount is set to always', async () => {
     const key = queryKey()
     const states: UseQueryResult<string>[] = []