Skip to content

Commit 4c741f7

Browse files
committed
feat: added queryCache.resetErrorBoundaries
Info can be found in the docs under the `Resetting Error Boundaries` section
1 parent 8519dd4 commit 4c741f7

File tree

8 files changed

+98
-78
lines changed

8 files changed

+98
-78
lines changed

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ This library is being built and maintained by me, @tannerlinsley and I am always
207207
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
208208
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
209209

210-
211210
- [Installation](#installation)
212211
- [Defaults to keep in mind](#defaults-to-keep-in-mind)
213212
- [Queries](#queries)
@@ -990,10 +989,31 @@ import { useQuery } from 'react-query'
990989
useQuery(queryKey, queryFn, { suspense: true })
991990
```
992991
993-
When using suspense mode, `status` states and `error` objects are not needed and are then 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/suspense) for more information on how to set up suspense mode.
992+
When using suspense mode, `status` states and `error` objects are not needed and are then replaced by usage of the `React.Suspense` component (including the use of the `fallback` prop and React error boundaries for catching errors). Please read the [Resetting Error Boundaries](#resetting-error-boundaries) and look at the [Suspense Example](https://codesandbox.io/s/github/tannerlinsley/react-query/tree/master/examples/suspense) for more information on how to set up suspense mode.
994993
995994
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!
996995
996+
## Resetting Error Boundaries
997+
998+
Whether you are using **suspense** or **useErrorBoundaries** in your queries, you will need to know how to use the `queryCache.resetErrorBoundaries` function to let queries know that you want them to try again when you render them again.
999+
1000+
How you trigger this function is up to you, but the most common use case is to do it in something like `react-error-boundary`'s `onReset` callback:
1001+
1002+
```js
1003+
import { queryCache } from "react-query";
1004+
import { ErrorBoundary } from "react-error-boundary";
1005+
1006+
<ErrorBoundary
1007+
onReset={() => queryCache.resetErrorBoundaries()}
1008+
fallbackRender={({ error, resetErrorBoundary }) => (
1009+
<div>
1010+
There was an error!
1011+
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
1012+
</div>
1013+
)}
1014+
>
1015+
```
1016+
9971017
## Fetch-on-render vs Fetch-as-you-render
9981018
9991019
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.

examples/suspense/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"axios": "0.19.2",
99
"react": "0.0.0-experimental-5faf377df",
1010
"react-dom": "0.0.0-experimental-5faf377df",
11-
"react-query": "latest",
11+
"react-error-boundary": "^2.2.3",
12+
"react-query": "^2.0.4",
1213
"react-scripts": "3.0.1"
1314
},
1415
"devDependencies": {

examples/suspense/src/components/ErrorBounderay.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

examples/suspense/src/index.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import React, { lazy } from "react";
22
import ReactDOM from "react-dom";
33
import { ReactQueryConfigProvider, queryCache } from "react-query";
4+
import { ErrorBoundary } from "react-error-boundary";
45

56
import "./styles.css";
67

78
import { fetchProjects } from "./queries";
89

9-
import ErrorBounderay from "./components/ErrorBounderay";
1010
import Button from "./components/Button";
1111

1212
const Projects = lazy(() => import("./components/Projects"));
1313
const Project = lazy(() => import("./components/Project"));
1414

1515
const queryConfig = {
1616
shared: {
17-
suspense: true
18-
}
17+
suspense: true,
18+
},
19+
queries: {
20+
retry: 0,
21+
},
1922
};
2023

2124
function App() {
@@ -26,7 +29,7 @@ function App() {
2629
<ReactQueryConfigProvider config={queryConfig}>
2730
<Button
2831
onClick={() => {
29-
setShowProjects(old => {
32+
setShowProjects((old) => {
3033
if (!old) {
3134
queryCache.prefetchQuery("projects", fetchProjects);
3235
}
@@ -39,7 +42,16 @@ function App() {
3942

4043
<hr />
4144

42-
<ErrorBounderay>
45+
<ErrorBoundary
46+
fallbackRender={({ error, resetErrorBoundary }) => (
47+
<div>
48+
There was an error!{" "}
49+
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
50+
<pre style={{ whiteSpace: "normal" }}>{error.message}</pre>
51+
</div>
52+
)}
53+
onReset={() => queryCache.resetErrorBoundaries()}
54+
>
4355
<React.Suspense fallback={<h1>Loading projects...</h1>}>
4456
{showProjects ? (
4557
activeProject ? (
@@ -52,7 +64,7 @@ function App() {
5264
)
5365
) : null}
5466
</React.Suspense>
55-
</ErrorBounderay>
67+
</ErrorBoundary>
5668
</ReactQueryConfigProvider>
5769
);
5870
}

examples/suspense/src/queries.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import axios from "axios";
22

3+
let count = 0;
4+
35
export async function fetchProjects(key) {
46
console.info("fetch projects");
7+
if (count < 4) {
8+
count++;
9+
throw new Error("testing");
10+
}
511
let { data } = await axios.get(
612
`https://api.github.com/users/tannerlinsley/repos?sort=updated`
713
);
8-
await new Promise(r => setTimeout(r, 1000));
14+
await new Promise((r) => setTimeout(r, 1000));
915
return data;
1016
}
1117

@@ -14,6 +20,6 @@ export async function fetchProject(key, { id }) {
1420
let { data } = await axios.get(
1521
`https://api.github.com/repos/tannerlinsley/${id}`
1622
);
17-
await new Promise(r => setTimeout(r, 1000));
23+
await new Promise((r) => setTimeout(r, 1000));
1824
return data;
1925
}

examples/suspense/yarn.lock

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,13 @@
891891
dependencies:
892892
regenerator-runtime "^0.13.2"
893893

894+
"@babel/runtime@^7.9.6":
895+
version "7.10.3"
896+
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364"
897+
integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==
898+
dependencies:
899+
regenerator-runtime "^0.13.4"
900+
894901
"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0":
895902
version "7.6.0"
896903
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
@@ -1165,6 +1172,11 @@
11651172
dependencies:
11661173
ramda "^0.26.0"
11671174

1175+
"@scarf/scarf@^1.0.6":
1176+
version "1.0.6"
1177+
resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.0.6.tgz#52011dfb19187b53b75b7b6eac20da0810ddd88f"
1178+
integrity sha512-y4+DuXrAd1W5UIY3zTcsosi/1GyYT8k5jGnZ/wG7UUHVrU+MHlH4Mp87KK2/lvMW4+H7HVcdB+aJhqywgXksjA==
1179+
11681180
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
11691181
version "4.2.0"
11701182
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1"
@@ -1321,31 +1333,11 @@
13211333
"@types/istanbul-lib-coverage" "*"
13221334
"@types/istanbul-lib-report" "*"
13231335

1324-
"@types/prop-types@*":
1325-
version "15.7.3"
1326-
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
1327-
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
1328-
13291336
"@types/q@^1.5.1":
13301337
version "1.5.2"
13311338
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
13321339
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
13331340

1334-
"@types/react-query@^0.3.0":
1335-
version "0.3.4"
1336-
resolved "https://registry.yarnpkg.com/@types/react-query/-/react-query-0.3.4.tgz#6a225b8f90e162deedaa434011c99d730d069046"
1337-
integrity sha512-lYBiEkfp+q/gSZ8XMm4YsnGvhMqBZSIUJxIQK9vMmAOCpjw7HbRDeMPC6WV0FuIhbXhTlamD3EepMOzujeChfg==
1338-
dependencies:
1339-
"@types/react" "*"
1340-
1341-
"@types/react@*":
1342-
version "16.9.16"
1343-
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.16.tgz#4f12515707148b1f53a8eaa4341dae5dfefb066d"
1344-
integrity sha512-dQ3wlehuBbYlfvRXfF5G+5TbZF3xqgkikK7DWAsQXe2KnzV+kjD4W2ea+ThCrKASZn9h98bjjPzoTYzfRqyBkw==
1345-
dependencies:
1346-
"@types/prop-types" "*"
1347-
csstype "^2.2.0"
1348-
13491341
"@types/stack-utils@^1.0.1":
13501342
version "1.0.1"
13511343
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
@@ -3078,11 +3070,6 @@ cssstyle@^1.0.0, cssstyle@^1.1.1:
30783070
dependencies:
30793071
cssom "0.3.x"
30803072

3081-
csstype@^2.2.0:
3082-
version "2.6.7"
3083-
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5"
3084-
integrity sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==
3085-
30863073
cyclist@^1.0.1:
30873074
version "1.0.1"
30883075
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
@@ -8074,6 +8061,13 @@ react-dom@0.0.0-experimental-5faf377df:
80748061
prop-types "^15.6.2"
80758062
scheduler "0.0.0-experimental-5faf377df"
80768063

8064+
react-error-boundary@^2.2.3:
8065+
version "2.2.3"
8066+
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-2.2.3.tgz#34c8238012d3b4148cec47a1b3cec669d5206578"
8067+
integrity sha512-Jiaiu6CJ4ho3sMCVI7gg+O/JB5vlFFZGwlnpFBTCOSyheYRTzz+FhBMo7tfnCTB/ZR0LaMzAPGbZGrEzAOd0eg==
8068+
dependencies:
8069+
"@babel/runtime" "^7.9.6"
8070+
80778071
react-error-overlay@^5.1.4:
80788072
version "5.1.6"
80798073
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.6.tgz#0cd73407c5d141f9638ae1e0c63e7b2bf7e9929d"
@@ -8089,12 +8083,13 @@ react-is@^16.8.1, react-is@^16.8.4:
80898083
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa"
80908084
integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==
80918085

8092-
react-query@latest:
8093-
version "0.3.22"
8094-
resolved "https://registry.yarnpkg.com/react-query/-/react-query-0.3.22.tgz#042df656c1571df128a818964122d90e4af55edb"
8095-
integrity sha512-x05TEfUAT69Qve7090IFBnqzYsl49s8+vx6sEpFAR2C0xdtUplr6ZAeSfya0SRft6ko4vJgktAJCTOV30AihPg==
8086+
react-query@^2.0.4:
8087+
version "2.0.4"
8088+
resolved "https://registry.yarnpkg.com/react-query/-/react-query-2.0.4.tgz#dcae7518efa4bdd31410e92ccd4f396d7398dfdc"
8089+
integrity sha512-6+hYuRWvPQlFrzoJgT5ZmlCoSxv3LqpiDXaSzbcAtwsEtcUFWz5DKL6L8EpYoreJS2INAQONEYHtXCt1tT4HlQ==
80968090
dependencies:
8097-
"@types/react-query" "^0.3.0"
8091+
"@scarf/scarf" "^1.0.6"
8092+
ts-toolbelt "^6.9.4"
80988093

80998094
react-scripts@3.0.1:
81008095
version "3.0.1"
@@ -8266,6 +8261,11 @@ regenerator-runtime@^0.11.0:
82668261
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
82678262
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
82688263

8264+
regenerator-runtime@^0.13.4:
8265+
version "0.13.5"
8266+
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
8267+
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
8268+
82698269
regenerator-transform@^0.14.0:
82708270
version "0.14.1"
82718271
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"
@@ -9452,6 +9452,11 @@ ts-pnp@^1.0.0:
94529452
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.4.tgz#ae27126960ebaefb874c6d7fa4729729ab200d90"
94539453
integrity sha512-1J/vefLC+BWSo+qe8OnJQfWTYRS6ingxjwqmHMqaMxXMj7kFtKLgAaYW3JeX3mktjgUL+etlU8/B4VUAUI9QGw==
94549454

9455+
ts-toolbelt@^6.9.4:
9456+
version "6.9.9"
9457+
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.9.9.tgz#e6cfd8ec7d425d2a06bda3b4fe9577ceaf2abda8"
9458+
integrity sha512-5a8k6qfbrL54N4Dw+i7M6kldrbjgDWb5Vit8DnT+gwThhvqMg8KtxLE5Vmnft+geIgaSOfNJyAcnmmlflS+Vdg==
9459+
94559460
tslib@^1.8.1, tslib@^1.9.0:
94569461
version "1.10.0"
94579462
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"

src/queryCache.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ export function makeQueryCache({ frozen = isServer, defaultConfig } = {}) {
152152
}
153153
}
154154

155+
queryCache.resetErrorBoundaries = () => {
156+
queryCache.getQueries(true).forEach(query => {
157+
query.state.throwInErrorBoundary = false
158+
})
159+
}
160+
155161
queryCache.buildQuery = (userQueryKey, queryFn, config = {}) => {
156162
config = {
157163
...configRef.current.shared,
@@ -665,6 +671,7 @@ function switchActions(state, action) {
665671
...(!action.cancelled && {
666672
status: statusError,
667673
error: action.error,
674+
throwInErrorBoundary: true,
668675
}),
669676
}
670677
case actionSetState:

src/utils.js

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,21 +128,14 @@ export function handleSuspense(queryInfo) {
128128
queryInfo.query.config.suspense ||
129129
queryInfo.query.config.useErrorBoundary
130130
) {
131-
if (queryInfo.query.state.status === statusError) {
132-
if (!queryInfo.query.suspenseErrorHandled) {
133-
queryInfo.query.suspenseErrorHandled = true
134-
135-
setTimeout(() => {
136-
queryInfo.query.state.status = statusLoading
137-
}, 0)
138-
139-
throw queryInfo.error
140-
}
131+
if (
132+
queryInfo.query.state.status === statusError &&
133+
queryInfo.query.state.throwInErrorBoundary
134+
) {
135+
throw queryInfo.error
141136
}
142137

143-
queryInfo.query.suspenseErrorHandled = false
144-
145-
if (queryInfo.query.config.suspense && queryInfo.status === statusLoading) {
138+
if (queryInfo.query.config.suspense && queryInfo.status !== statusSuccess) {
146139
queryInfo.query.wasSuspended = true
147140
throw queryInfo.query.fetch()
148141
}

0 commit comments

Comments
 (0)