Skip to content

Commit c4bcdac

Browse files
committed
Added useErrorBoundary and throwOnError options
1 parent da31578 commit c4bcdac

File tree

3 files changed

+47
-9
lines changed

3 files changed

+47
-9
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 0.3.28
4+
5+
- Added the `useMutation.throwOnError` and corresponding `queryConfig.throwOnError` option to configure whether the `mutate` function rethrows errors encountered in the mutation function
6+
- Added the `useMutation.useErrorBoundary` and corresponding `queryConfig.useErrorBoundary` option to configure whether mutation errors should be thrown during the render function and propagated to the nearest error boundary. This option will default to the same value as `queryConfig.suspense` if not defined otherwise
7+
38
## 0.3.27
49

510
- Switched from the fast-async babel plugin to the babel-plugin-transform-async-to-promises. This should offer better compiler/browser support at the expense of 0.1kb

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,8 @@ useQuery(queryKey, queryFn, { suspense: true })
697697
698698
When using suspense mode, `isLoading` and `error` states will be replaced by usage of the `React.Suspense` component (including the use of the `fallback` prop and React error boundaries for catching errors). Please see the [Suspense Example](https://codesandbox.io/s/github/tannerlinsley/react-query/tree/master/examples/sandbox) for more information on how to set up suspense mode.
699699
700+
In addition to queries behaving differently in suspense mode, mutations also behave a bit differently. By default, instead of supplying the `error` variable when a mutation fails, it will be thrown during the next render of the component it's used in and propagate to the nearest error boundary, similar to query errors. If you wish to disable this, you can set the `useErrorBoundary` option to `false`. If you wish that errors are not thrown at all, you can set the `throwOnError` option to `false` as well!
701+
700702
### Fetch-on-render vs Fetch-as-you-render
701703
702704
Out of the box, React Query in `suspense` mode works really well as a **Fetch-on-render** solution with no additional configuration. However, if you want to take it to the next level and implement a `Fetch-as-you-render` model, we recommend implementing [Prefetching](#prefetching) on routing and/or user interactions events to initialize queries before they are needed.
@@ -1371,6 +1373,8 @@ const {
13711373
const [mutate, { data, isLoading, error }] = useMutation(mutationFn, {
13721374
refetchQueries,
13731375
refetchQueriesOnFailure,
1376+
useErrorBoundary,
1377+
throwOnError,
13741378
})
13751379

13761380
const promise = mutate(variables, { updateQuery, waitForRefetchQueries })
@@ -1388,6 +1392,12 @@ const promise = mutate(variables, { updateQuery, waitForRefetchQueries })
13881392
- `refetchQueriesOnFailure: Boolean`
13891393
- Defaults to `false`
13901394
- Set this to `true` if you want `refetchQueries` to be refetched regardless of the mutation succeeding.
1395+
- `useErrorBoundary`
1396+
- Defaults to the global query config's `useErrorBoundary` value, which is `false`
1397+
- Set this to true if you want mutation errors to be thrown in the render phase and propagate to the nearest error boundary
1398+
- `throwOnError`
1399+
- Defaults to `true` (but will be `false` in the next major release)
1400+
- Set this to `true` if failed mutations should re-throw errors from the mutation function to the `mutate` function.
13911401
- `variables: any`
13921402
- Optional
13931403
- The variables object to pass to the `mutationFn`.
@@ -1564,6 +1574,8 @@ const queryConfig = {
15641574
refetchAllOnWindowFocus: true,
15651575
refetchInterval: false,
15661576
suspense: false,
1577+
useErrorBoundary: undefined, // Defaults to the value of `suspense` if not defined otherwise
1578+
throwOnError: true,
15671579
}
15681580

15691581
function App() {
@@ -1579,4 +1591,4 @@ function App() {
15791591
15801592
- `config: Object`
15811593
- Must be **stable** or **memoized**. Do not create an inline object!
1582-
- For a description of all config options, please see the [`useQuery` hook](#usequery).
1594+
- For a description of all config options, please see their usage in both the [`useQuery` hook](#usequery) and the [`useMutation` hook](#usemutation).

src/index.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ let defaultConfig = {
1616
refetchInterval: false,
1717
suspense: false,
1818
queryKeySerializerFn: defaultQueryKeySerializerFn,
19+
throwOnError: true,
20+
useErrorBoundary: undefined, // this will default to the suspense value
1921
}
2022

2123
const onWindowFocus = () => {
@@ -64,13 +66,19 @@ setFocusHandler(handleFocus => {
6466
export function ReactQueryConfigProvider({ config, children }) {
6567
let configContextValue = React.useContext(configContext)
6668

67-
const newConfig = React.useMemo(
68-
() => ({
69+
const newConfig = React.useMemo(() => {
70+
const newConfig = {
6971
...(configContextValue || defaultConfig),
7072
...config,
71-
}),
72-
[config, configContextValue]
73-
)
73+
}
74+
75+
// Default useErrorBoundary to the suspense value
76+
if (typeof newConfig.useErrorBoundary === 'undefined') {
77+
newConfig.useErrorBoundary = newConfig.suspense
78+
}
79+
80+
return newConfig
81+
}, [config, configContextValue])
7482

7583
if (!configContextValue) {
7684
defaultConfig = newConfig
@@ -612,14 +620,19 @@ export async function refetchQuery(queryKey, config = {}) {
612620

613621
export function useMutation(
614622
mutationFn,
615-
{ refetchQueries, refetchQueriesOnFailure } = {}
623+
{ refetchQueries, refetchQueriesOnFailure, ...config } = {}
616624
) {
617625
const [data, setData] = React.useState(null)
618626
const [error, setError] = React.useState(null)
619627
const [isLoading, setIsLoading] = React.useState(false)
620628
const mutationFnRef = React.useRef()
621629
mutationFnRef.current = mutationFn
622630

631+
const { throwOnError, useErrorBoundary } = {
632+
...useConfigContext(),
633+
...config,
634+
}
635+
623636
const mutate = React.useCallback(
624637
async (variables, { updateQuery, waitForRefetchQueries = false } = {}) => {
625638
setIsLoading(true)
@@ -662,14 +675,22 @@ export function useMutation(
662675
}
663676

664677
setIsLoading(false)
665-
throw error
678+
if (throwOnError) {
679+
throw error
680+
}
666681
}
667682
},
668-
[refetchQueriesOnFailure, refetchQueries]
683+
[refetchQueries, refetchQueriesOnFailure, throwOnError]
669684
)
670685

671686
const reset = React.useCallback(() => setData(null), [])
672687

688+
React.useEffect(() => {
689+
if (useErrorBoundary && error) {
690+
throw error
691+
}
692+
}, [error, useErrorBoundary])
693+
673694
return [mutate, { data, isLoading, error, reset }]
674695
}
675696

0 commit comments

Comments
 (0)