Skip to content
This repository was archived by the owner on Nov 23, 2024. It is now read-only.

Commit 5f45a38

Browse files
phryneasmsutkowski
andauthored
Implement retrying (#64)
* Add a basic exponential backoff, additional tests and helpers, example (#66) * Add error handling copy * Allow for a custom backoff fn * Add video links and fix config * Add additional tests for backoff fn Co-authored-by: Matt Sutkowski <[email protected]>
1 parent 5fd5fff commit 5f45a38

24 files changed

+892
-175
lines changed

.vscode/settings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
"typescript.tsdk": "node_modules/typescript/lib",
33
"jest.pathToJest": "npm test --",
44
"jest.enableInlineErrorMessages": true,
5-
"jest.autoEnable": false
5+
"jest.autoEnable": false,
6+
"editor.formatOnSave": true
67
}

docs/concepts/error-handling.md

+175-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,179 @@ sidebar_label: Error Handling
55
hide_title: true
66
---
77

8-
# Error Handling
8+
# `Error Handling`
99

10-
## Coming Soon
10+
If your query or mutation happens to throw an error when using [fetchBaseQuery](../api/fetchBaseQuery), it will be returned in the `error` property of the respective hook.
11+
12+
```ts title="Query Error"
13+
function PostsList() {
14+
const { data, error } = useGetPostsQuery();
15+
16+
return (
17+
<div>
18+
{error.status} {JSON.stringify(error.data)}
19+
</div>
20+
);
21+
}
22+
```
23+
24+
```ts title="Mutation Error"
25+
function AddPost() {
26+
const [addPost, { error }] = useAddPostMutation();
27+
28+
return (
29+
<div>
30+
{error.status} {JSON.stringify(error.data)}
31+
</div>
32+
);
33+
}
34+
```
35+
36+
```ts title="Manually selecting an error"
37+
function PostsList() {
38+
const { error } = useSelector(api.endpoints.getPosts.select());
39+
40+
return (
41+
<div>
42+
{error.status} {JSON.stringify(error.data)}
43+
</div>
44+
);
45+
}
46+
```
47+
48+
## Errors with a custom `baseQuery`
49+
50+
By default, RTK Query expects you to `return` two possible objects:
51+
52+
1. ```ts title="Expected success result format"
53+
return { data: { first_name: 'Randy', last_name: 'Banana' };
54+
```
55+
2. ```ts title="Expected error result format"
56+
return { error: { status: 500, data: { message: 'Failed because of reasons' } };
57+
```
58+
59+
:::note
60+
This format is required so that RTK Query can infer the return types for your responses.
61+
:::
62+
63+
As an example, this what a very basic axios-based `baseQuery` utility could look like:
64+
65+
```ts title="Basic axios baseQuery"
66+
const axiosBaseQuery = (
67+
{ baseUrl }: { baseUrl: string } = { baseUrl: '' }
68+
): BaseQueryFn<
69+
{
70+
url: string;
71+
method: AxiosRequestConfig['method'];
72+
data?: AxiosRequestConfig['data'];
73+
},
74+
unknown,
75+
unknown
76+
> => async ({ url, method, data }) => {
77+
try {
78+
const result = await axios({ url: baseUrl + url, method, data });
79+
return { data: result.data };
80+
} catch (axiosError) {
81+
let err = axiosError as AxiosError;
82+
return { error: { status: err.response?.status, data: err.response?.data } };
83+
}
84+
};
85+
86+
const api = createApi({
87+
baseQuery: axiosBaseQuery({
88+
baseUrl: 'http://example.com',
89+
}),
90+
endpoints(build) {
91+
return {
92+
query: build.query({ query: () => ({ url: '/query' }) }),
93+
mutation: build.mutation({ query: () => ({ url: '/mutation', method: 'post' }) }),
94+
};
95+
},
96+
});
97+
```
98+
99+
Ultimately, you can choose whatever library you prefer to use with your `baseQuery`, but it's important that you return the correct response format. If you haven't tried [`fetchBaseQuery`](../api/fetchBaseQuery) yet, give it a chance!
100+
101+
## Retrying on Error
102+
103+
RTK Query exports a utility called `retry` that you can wrap the `baseQuery` in your API definition with. It defaults to 5 attempts with a basic exponential backoff.
104+
105+
The default behavior would retry at these intervals:
106+
107+
1. 600ms + random time
108+
2. 1200ms + random time
109+
3. 2400ms + random time
110+
4. 4800ms + random time
111+
5. 9600ms + random time
112+
113+
```ts title="Retry every request 5 times by default"
114+
// maxRetries: 5 is the default, and can be omitted. Shown for documentation purposes.
115+
const staggeredBaseQuery = retry(fetchBaseQuery({ baseUrl: '/' }), { maxRetries: 5 });
116+
117+
export const api = createApi({
118+
baseQuery: staggeredBaseQuery,
119+
endpoints: (build) => ({
120+
getPosts: build.query<PostsResponse, void>({
121+
query: () => ({ url: 'posts' }),
122+
}),
123+
getPost: build.query<PostsResponse, void>({
124+
query: (id: string) => ({ url: `posts/${id}` }),
125+
extraOptions: { maxRetries: 8 }, // You can override the retry behavior on each endpoint
126+
}),
127+
}),
128+
});
129+
130+
export const { useGetPostsQuery, useGetPostQuery } = api;
131+
```
132+
133+
In the event that you didn't want to retry on a specific endpoint, you can just set `maxRetries: 0`.
134+
135+
:::info
136+
It is possible for a hook to return `data` and `error` at the same time. By default, RTK Query will keep whatever the last 'good' result was in `data` until it can be updated or garbage collected.
137+
:::
138+
139+
## Bailing out of errors
140+
141+
`retry.fail`
142+
143+
```ts title="TODO"
144+
baseBaseQuery.mockImplementation((input) => {
145+
retry.fail(error);
146+
return { data: `this won't happen` };
147+
});
148+
149+
const baseQuery = retry(baseBaseQuery);
150+
const api = createApi({
151+
baseQuery,
152+
endpoints: (build) => ({
153+
q1: build.query({
154+
query: () => {},
155+
}),
156+
}),
157+
});
158+
```
159+
160+
## Handling errors at a macro level
161+
162+
There are quite a few ways that you can manage your errors, and in some cases, you may want to show a generic toast notification for any async error. Being that RTK Query is built on top of Redux and Redux-Toolkit, you can easily add a middleware to your store for this purpose.
163+
164+
:::tip
165+
Redux-Toolkit released [matching utilities](https://redux-toolkit.js.org/api/matching-utilities#matching-utilities) in v1.5 that we can leverage for a lot of custom behaviors.
166+
:::
167+
168+
```ts title="Error catching middleware"
169+
import { MiddlewareAPI, isRejectedWithValue } from '@reduxjs/toolkit';
170+
import { toast } from 'your-cool-library';
171+
/**
172+
* Log a warning and show a toast!
173+
*/
174+
export const rtkQueryErrorLogger = (api: MiddlewareAPI) => (next) => (action) => {
175+
// RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these use matchers!
176+
if (isRejectedWithValue(action)) {
177+
console.warn('We got a rejected action!');
178+
toast.warn({ title: 'Async error!', message: action.error.data.message });
179+
}
180+
181+
return next(action);
182+
};
183+
```

docs/concepts/mutations.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ When `addPost` is fired, it will only cause the `PostsList` to go into an `isFet
234234
This is an example of a [CRUD service](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) for Posts. This implements the [Selectively invalidating lists](#selectively-invalidating-lists) strategy and will most likely serve as a good foundation for real applications.
235235
236236
```ts title="src/app/services/posts.ts"
237-
import { createApi, fetchBaseQuery } from '@rtk-incubator/simple-query/dist';
237+
import { createApi, fetchBaseQuery } from '@rtk-incubator/rtk-query';
238238

239239
export interface Post {
240240
id: number;

docs/examples/react-authentication.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
id: react-authentication
3+
title: React Authentication
4+
sidebar_label: React Authentication
5+
hide_title: true
6+
hide_table_of_contents: true
7+
---
8+
9+
# `React Authentication Example`
10+
11+
Coming soon...

docs/introduction/video-overview.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
id: video-overview
3+
title: Video Overview
4+
sidebar_label: Video Overview
5+
hide_title: true
6+
hide_table_of_contents: true
7+
---
8+
9+
# Video Overview
10+
11+
[Lenz Weber](https://twitter.com/phry) takes you on a brief tour of the basics for getting up and running with RTK Query.
12+
13+
## 1. Setting up and writing a query
14+
15+
<iframe width="560" height="315" src="https://www.youtube.com/embed/FDEgXmx1V4A" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
16+
17+
## 2. Basic mutations
18+
19+
<iframe width="560" height="315" src="https://www.youtube.com/embed/eSs-XslROR8" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
20+
21+
## 3. Basic invalidation
22+
23+
<iframe width="560" height="315" src="https://www.youtube.com/embed/OCcGvg2I5E8" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

0 commit comments

Comments
 (0)