Skip to content

Commit

Permalink
feat: added queryCache.resetErrorBoundaries
Browse files Browse the repository at this point in the history
Info can be found in the docs under the `Resetting Error Boundaries` section
  • Loading branch information
tannerlinsley committed Jun 24, 2020
1 parent 8519dd4 commit 4c741f7
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 78 deletions.
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ This library is being built and maintained by me, @tannerlinsley and I am always
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Installation](#installation)
- [Defaults to keep in mind](#defaults-to-keep-in-mind)
- [Queries](#queries)
Expand Down Expand Up @@ -990,10 +989,31 @@ import { useQuery } from 'react-query'
useQuery(queryKey, queryFn, { suspense: true })
```
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.
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.
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!
## Resetting Error Boundaries
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.
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:
```js
import { queryCache } from "react-query";
import { ErrorBoundary } from "react-error-boundary";

<ErrorBoundary
onReset={() => queryCache.resetErrorBoundaries()}
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
There was an error!
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
</div>
)}
>
```
## Fetch-on-render vs Fetch-as-you-render
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.
Expand Down
3 changes: 2 additions & 1 deletion examples/suspense/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"axios": "0.19.2",
"react": "0.0.0-experimental-5faf377df",
"react-dom": "0.0.0-experimental-5faf377df",
"react-query": "latest",
"react-error-boundary": "^2.2.3",
"react-query": "^2.0.4",
"react-scripts": "3.0.1"
},
"devDependencies": {
Expand Down
24 changes: 0 additions & 24 deletions examples/suspense/src/components/ErrorBounderay.js

This file was deleted.

24 changes: 18 additions & 6 deletions examples/suspense/src/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import React, { lazy } from "react";
import ReactDOM from "react-dom";
import { ReactQueryConfigProvider, queryCache } from "react-query";
import { ErrorBoundary } from "react-error-boundary";

import "./styles.css";

import { fetchProjects } from "./queries";

import ErrorBounderay from "./components/ErrorBounderay";
import Button from "./components/Button";

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

const queryConfig = {
shared: {
suspense: true
}
suspense: true,
},
queries: {
retry: 0,
},
};

function App() {
Expand All @@ -26,7 +29,7 @@ function App() {
<ReactQueryConfigProvider config={queryConfig}>
<Button
onClick={() => {
setShowProjects(old => {
setShowProjects((old) => {
if (!old) {
queryCache.prefetchQuery("projects", fetchProjects);
}
Expand All @@ -39,7 +42,16 @@ function App() {

<hr />

<ErrorBounderay>
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
There was an error!{" "}
<Button onClick={() => resetErrorBoundary()}>Try again</Button>
<pre style={{ whiteSpace: "normal" }}>{error.message}</pre>
</div>
)}
onReset={() => queryCache.resetErrorBoundaries()}
>
<React.Suspense fallback={<h1>Loading projects...</h1>}>
{showProjects ? (
activeProject ? (
Expand All @@ -52,7 +64,7 @@ function App() {
)
) : null}
</React.Suspense>
</ErrorBounderay>
</ErrorBoundary>
</ReactQueryConfigProvider>
);
}
Expand Down
10 changes: 8 additions & 2 deletions examples/suspense/src/queries.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import axios from "axios";

let count = 0;

export async function fetchProjects(key) {
console.info("fetch projects");
if (count < 4) {
count++;
throw new Error("testing");
}
let { data } = await axios.get(
`https://api.github.com/users/tannerlinsley/repos?sort=updated`
);
await new Promise(r => setTimeout(r, 1000));
await new Promise((r) => setTimeout(r, 1000));
return data;
}

Expand All @@ -14,6 +20,6 @@ export async function fetchProject(key, { id }) {
let { data } = await axios.get(
`https://api.github.com/repos/tannerlinsley/${id}`
);
await new Promise(r => setTimeout(r, 1000));
await new Promise((r) => setTimeout(r, 1000));
return data;
}
65 changes: 35 additions & 30 deletions examples/suspense/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,13 @@
dependencies:
regenerator-runtime "^0.13.2"

"@babel/runtime@^7.9.6":
version "7.10.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364"
integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
Expand Down Expand Up @@ -1165,6 +1172,11 @@
dependencies:
ramda "^0.26.0"

"@scarf/scarf@^1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.0.6.tgz#52011dfb19187b53b75b7b6eac20da0810ddd88f"
integrity sha512-y4+DuXrAd1W5UIY3zTcsosi/1GyYT8k5jGnZ/wG7UUHVrU+MHlH4Mp87KK2/lvMW4+H7HVcdB+aJhqywgXksjA==

"@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1"
Expand Down Expand Up @@ -1321,31 +1333,11 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"

"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==

"@types/q@^1.5.1":
version "1.5.2"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==

"@types/react-query@^0.3.0":
version "0.3.4"
resolved "https://registry.yarnpkg.com/@types/react-query/-/react-query-0.3.4.tgz#6a225b8f90e162deedaa434011c99d730d069046"
integrity sha512-lYBiEkfp+q/gSZ8XMm4YsnGvhMqBZSIUJxIQK9vMmAOCpjw7HbRDeMPC6WV0FuIhbXhTlamD3EepMOzujeChfg==
dependencies:
"@types/react" "*"

"@types/react@*":
version "16.9.16"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.16.tgz#4f12515707148b1f53a8eaa4341dae5dfefb066d"
integrity sha512-dQ3wlehuBbYlfvRXfF5G+5TbZF3xqgkikK7DWAsQXe2KnzV+kjD4W2ea+ThCrKASZn9h98bjjPzoTYzfRqyBkw==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"

"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
Expand Down Expand Up @@ -3078,11 +3070,6 @@ cssstyle@^1.0.0, cssstyle@^1.1.1:
dependencies:
cssom "0.3.x"

csstype@^2.2.0:
version "2.6.7"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5"
integrity sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==

cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
Expand Down Expand Up @@ -8074,6 +8061,13 @@ react-dom@0.0.0-experimental-5faf377df:
prop-types "^15.6.2"
scheduler "0.0.0-experimental-5faf377df"

react-error-boundary@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-2.2.3.tgz#34c8238012d3b4148cec47a1b3cec669d5206578"
integrity sha512-Jiaiu6CJ4ho3sMCVI7gg+O/JB5vlFFZGwlnpFBTCOSyheYRTzz+FhBMo7tfnCTB/ZR0LaMzAPGbZGrEzAOd0eg==
dependencies:
"@babel/runtime" "^7.9.6"

react-error-overlay@^5.1.4:
version "5.1.6"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.6.tgz#0cd73407c5d141f9638ae1e0c63e7b2bf7e9929d"
Expand All @@ -8089,12 +8083,13 @@ react-is@^16.8.1, react-is@^16.8.4:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa"
integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==

react-query@latest:
version "0.3.22"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-0.3.22.tgz#042df656c1571df128a818964122d90e4af55edb"
integrity sha512-x05TEfUAT69Qve7090IFBnqzYsl49s8+vx6sEpFAR2C0xdtUplr6ZAeSfya0SRft6ko4vJgktAJCTOV30AihPg==
react-query@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-2.0.4.tgz#dcae7518efa4bdd31410e92ccd4f396d7398dfdc"
integrity sha512-6+hYuRWvPQlFrzoJgT5ZmlCoSxv3LqpiDXaSzbcAtwsEtcUFWz5DKL6L8EpYoreJS2INAQONEYHtXCt1tT4HlQ==
dependencies:
"@types/react-query" "^0.3.0"
"@scarf/scarf" "^1.0.6"
ts-toolbelt "^6.9.4"

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

regenerator-runtime@^0.13.4:
version "0.13.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==

regenerator-transform@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"
Expand Down Expand Up @@ -9452,6 +9452,11 @@ ts-pnp@^1.0.0:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.4.tgz#ae27126960ebaefb874c6d7fa4729729ab200d90"
integrity sha512-1J/vefLC+BWSo+qe8OnJQfWTYRS6ingxjwqmHMqaMxXMj7kFtKLgAaYW3JeX3mktjgUL+etlU8/B4VUAUI9QGw==

ts-toolbelt@^6.9.4:
version "6.9.9"
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.9.9.tgz#e6cfd8ec7d425d2a06bda3b4fe9577ceaf2abda8"
integrity sha512-5a8k6qfbrL54N4Dw+i7M6kldrbjgDWb5Vit8DnT+gwThhvqMg8KtxLE5Vmnft+geIgaSOfNJyAcnmmlflS+Vdg==

tslib@^1.8.1, tslib@^1.9.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
Expand Down
7 changes: 7 additions & 0 deletions src/queryCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ export function makeQueryCache({ frozen = isServer, defaultConfig } = {}) {
}
}

queryCache.resetErrorBoundaries = () => {
queryCache.getQueries(true).forEach(query => {
query.state.throwInErrorBoundary = false
})
}

queryCache.buildQuery = (userQueryKey, queryFn, config = {}) => {
config = {
...configRef.current.shared,
Expand Down Expand Up @@ -665,6 +671,7 @@ function switchActions(state, action) {
...(!action.cancelled && {
status: statusError,
error: action.error,
throwInErrorBoundary: true,
}),
}
case actionSetState:
Expand Down
19 changes: 6 additions & 13 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,14 @@ export function handleSuspense(queryInfo) {
queryInfo.query.config.suspense ||
queryInfo.query.config.useErrorBoundary
) {
if (queryInfo.query.state.status === statusError) {
if (!queryInfo.query.suspenseErrorHandled) {
queryInfo.query.suspenseErrorHandled = true

setTimeout(() => {
queryInfo.query.state.status = statusLoading
}, 0)

throw queryInfo.error
}
if (
queryInfo.query.state.status === statusError &&
queryInfo.query.state.throwInErrorBoundary
) {
throw queryInfo.error
}

queryInfo.query.suspenseErrorHandled = false

if (queryInfo.query.config.suspense && queryInfo.status === statusLoading) {
if (queryInfo.query.config.suspense && queryInfo.status !== statusSuccess) {
queryInfo.query.wasSuspended = true
throw queryInfo.query.fetch()
}
Expand Down

0 comments on commit 4c741f7

Please sign in to comment.