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

Commit 8f84890

Browse files
Enhance/fetch base query (#7)
- Enhances `fetchBaseQuery` to have a more familiar API
1 parent c65bc15 commit 8f84890

17 files changed

+207
-57
lines changed

examples/posts-and-counter/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"keywords": [],
66
"main": "./index.html",
77
"dependencies": {
8-
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit",
8+
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit",
99
"@rtk-incubator/simple-query": "https://pkg.csb.dev/rtk-incubator/simple-query/commit/fcea624c/@rtk-incubator/simple-query",
1010
"msw": "0.21.3",
1111
"react": "17.0.0",

examples/posts-and-counter/yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,9 +1369,9 @@
13691369
resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca"
13701370
integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==
13711371

1372-
"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit":
1372+
"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit":
13731373
version "1.4.0"
1374-
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit#4e2beed60d6e564d2911d475d3d0055b07b914a1"
1374+
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit#4ef0bb45ceca425759da283a7de3377ef369f17c"
13751375
dependencies:
13761376
immer "^7.0.3"
13771377
redux "^4.0.0"

examples/svelte-counter/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"typescript": "^3.9.3"
2727
},
2828
"dependencies": {
29-
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit",
29+
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit",
3030
"sirv-cli": "^1.0.0"
3131
}
3232
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
22
"template": "node"
3-
}
3+
}

examples/svelte-counter/src/App.svelte

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
1818
onMount(async () => {
1919
({ refetch: getCount } = store.dispatch(counterApi.queryActions.getCount()));
20+
store.dispatch(counterApi.queryActions.getAbsoluteTest())
21+
store.dispatch(counterApi.queryActions.getError());
22+
store.dispatch(counterApi.queryActions.getNetworkError());
23+
store.dispatch(counterApi.queryActions.getHeaderError());
2024
});
2125
</script>
2226

examples/svelte-counter/src/mocks/handlers.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@ let count = 0;
66
let counters = {};
77

88
export const handlers = [
9+
rest.get('/error', (req, res, ctx) => {
10+
return res(
11+
ctx.status(500),
12+
ctx.json({
13+
message: 'what is this doing!',
14+
data: [{ some: 'key' }],
15+
}),
16+
);
17+
}),
18+
rest.get('/network-error', (req, res, ctx) => {
19+
return res.networkError('Fake network error');
20+
}),
21+
rest.get('/mismatched-header-error', (req, res, ctx) => {
22+
return res(ctx.text('oh hello there'), ctx.set('Content-Type', 'application/hal+banana'));
23+
}),
24+
rest.get('https://mocked.data', (req, res, ctx) => {
25+
return res(
26+
ctx.json({
27+
great: 'success',
28+
}),
29+
);
30+
}),
931
rest.put<{ amount: number }>('/increment', (req, res, ctx) => {
1032
const { amount } = req.body;
1133
count = count += amount;

examples/svelte-counter/src/services/counter.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,55 +6,72 @@ interface CountResponse {
66

77
export const counterApi = createApi({
88
reducerPath: 'counterApi',
9-
baseQuery: fetchBaseQuery(),
9+
baseQuery: fetchBaseQuery({
10+
baseUrl: '/',
11+
}),
1012
entityTypes: ['Counter'],
1113
endpoints: (build) => ({
14+
getError: build.query({
15+
query: (_: void) => '/error',
16+
}),
17+
getNetworkError: build.query({
18+
query: (_: void) => '/network-error',
19+
}),
20+
getHeaderError: build.query({
21+
query: (_: void) => '/mismatched-header-error',
22+
}),
23+
getAbsoluteTest: build.query<any, void>({
24+
query: () => ({
25+
url: 'https://mocked.data',
26+
params: {
27+
hello: 'friend',
28+
},
29+
}),
30+
}),
1231
getCount: build.query<CountResponse, void>({
13-
query: () => 'count',
32+
query: () => ({
33+
url: `/count?=${'whydothis'}`,
34+
params: {
35+
test: 'param',
36+
additional: 1,
37+
},
38+
}),
1439
provides: ['Counter'],
1540
}),
16-
getCountById: build.query<CountResponse, number>({
17-
query: (id: number) => `${id}`,
18-
provides: (_, id) => [{ type: 'Counter', id }],
19-
}),
2041
incrementCount: build.mutation<CountResponse, number>({
21-
query(amount) {
22-
return {
23-
url: `increment`,
42+
query: (amount) => ({
43+
url: `/increment`,
44+
method: 'PUT',
45+
body: { amount },
46+
}),
47+
invalidates: ['Counter'],
48+
}),
49+
decrementCount: build.mutation<CountResponse, number>({
50+
query: (amount) => ({
51+
url: `decrement`,
2452
method: 'PUT',
25-
body: JSON.stringify({ amount }),
26-
};
27-
},
53+
body: { amount },
54+
}),
2855
invalidates: ['Counter'],
2956
}),
57+
getCountById: build.query<CountResponse, number>({
58+
query: (id: number) => `${id}`,
59+
provides: (_, id) => [{ type: 'Counter', id }],
60+
}),
3061
incrementCountById: build.mutation<CountResponse, { id: number; amount: number }>({
31-
query({ id, amount }) {
32-
return {
62+
query: ({ id, amount }) => ({
3363
url: `${id}/increment`,
3464
method: 'PUT',
35-
body: JSON.stringify({ amount }),
36-
};
37-
},
65+
body: { amount },
66+
}),
3867
invalidates: (_, { id }) => [{ type: 'Counter', id }],
3968
}),
40-
decrementCount: build.mutation<CountResponse, number>({
41-
query(amount) {
42-
return {
43-
url: `decrement`,
44-
method: 'PUT',
45-
body: JSON.stringify({ amount }),
46-
};
47-
},
48-
invalidates: ['Counter'],
49-
}),
5069
decrementCountById: build.mutation<CountResponse, { id: number; amount: number }>({
51-
query({ id, amount }) {
52-
return {
70+
query: ({ id, amount }) => ({
5371
url: `${id}/decrement`,
5472
method: 'PUT',
55-
body: JSON.stringify({ amount }),
56-
};
57-
},
73+
body: { amount },
74+
}),
5875
invalidates: (_, { id }) => [{ type: 'Counter', id }],
5976
}),
6077
}),

examples/svelte-counter/yarn.lock

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@
3333
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71"
3434
integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==
3535

36-
"@reduxjs/toolkit@^1.4.0":
36+
"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit":
3737
version "1.4.0"
38-
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.4.0.tgz#ee2e2384cc3d1d76780d844b9c2da3580d32710d"
39-
integrity sha512-hkxQwVx4BNVRsYdxjNF6cAseRmtrkpSlcgJRr3kLUcHPIAMZAmMJkXmHh/eUEGTMqPzsYpJLM7NN2w9fxQDuGw==
38+
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit#4ef0bb45ceca425759da283a7de3377ef369f17c"
4039
dependencies:
4140
immer "^7.0.3"
4241
redux "^4.0.0"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"react-redux": "^7.2.1"
5151
},
5252
"devDependencies": {
53-
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit",
53+
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit",
5454
"@size-limit/preset-small-lib": "^4.6.0",
5555
"@testing-library/react": "^11.1.0",
5656
"@testing-library/react-hooks": "^3.4.2",

src/buildActionMaps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ function assertIsNewRTKPromise(action: ReturnType<ThunkAction<any, any, any, any
194194
You are running a version of RTK that is too old.
195195
Currently you need an experimental build of RTK.
196196
Please install it via
197-
yarn add "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit"
197+
yarn add "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit"
198198
`);
199199
}
200200
}

src/fetchBaseQuery.ts

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,65 @@
11
import { QueryApi } from './buildThunks';
2+
import { joinUrls } from './utils';
3+
import { isPlainObject } from '@reduxjs/toolkit';
24

35
interface FetchArgs extends RequestInit {
46
url: string;
7+
params?: Record<string, any>;
8+
body?: any;
9+
responseHandler?: 'json' | 'text' | ((response: Response) => Promise<any>);
10+
validateStatus?: (response: Response, body: any) => boolean;
511
}
612

7-
export function fetchBaseQuery({ baseUrl }: { baseUrl: string } = { baseUrl: '' }) {
13+
const defaultValidateStatus = (response: Response) => response.status >= 200 && response.status <= 299;
14+
15+
const isJsonContentType = (headers: Headers) => headers.get('content-type')?.trim()?.startsWith('application/json');
16+
17+
export function fetchBaseQuery({ baseUrl }: { baseUrl?: string } = {}) {
818
return async (arg: string | FetchArgs, { signal, rejectWithValue }: QueryApi) => {
9-
const { url, method = 'GET', ...rest } = typeof arg == 'string' ? { url: arg } : arg;
10-
const result = await fetch(`${baseUrl}/${url}`, {
19+
let {
20+
url,
21+
method = 'GET' as const,
22+
headers = undefined,
23+
body = undefined,
24+
params = undefined,
25+
responseHandler = 'json' as const,
26+
validateStatus = defaultValidateStatus,
27+
...rest
28+
} = typeof arg == 'string' ? { url: arg } : arg;
29+
let config: RequestInit = {
1130
method,
12-
headers: {
13-
'Content-Type': 'application/json',
14-
},
1531
signal,
32+
body,
1633
...rest,
17-
});
34+
};
35+
36+
config.headers = new Headers(headers);
37+
38+
if (!config.headers.has('content-type')) {
39+
config.headers.set('content-type', 'application/json');
40+
}
41+
42+
if (body && isPlainObject(body) && isJsonContentType(config.headers)) {
43+
config.body = JSON.stringify(body);
44+
}
45+
46+
if (params) {
47+
const divider = ~url.indexOf('?') ? '&' : '?';
48+
const query = new URLSearchParams(params);
49+
url += divider + query;
50+
}
51+
52+
url = joinUrls(baseUrl, url);
53+
54+
const response = await fetch(url, config);
1855

19-
let resultData =
20-
result.headers.has('Content-Type') && !result.headers.get('Content-Type')?.trim()?.startsWith('application/json')
21-
? await result.text()
22-
: await result.json();
56+
const resultData =
57+
typeof responseHandler === 'function'
58+
? await responseHandler(response)
59+
: await response[responseHandler || 'text']();
2360

24-
return result.status >= 200 && result.status <= 299
61+
return validateStatus(response, resultData)
2562
? resultData
26-
: rejectWithValue({ status: result.status, data: resultData });
63+
: rejectWithValue({ status: response.status, data: resultData });
2764
};
2865
}

src/utils/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './isAbsoluteUrl';
2+
export * from './isValidUrl';
3+
export * from './joinUrls';

src/utils/isAbsoluteUrl.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* If either :// or // is present consider it to be an absolute url
3+
*
4+
* @param url string
5+
*/
6+
7+
export function isAbsoluteUrl(url: string) {
8+
return new RegExp(`(^|:)//`).test(url);
9+
}

src/utils/isValidUrl.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function isValidUrl(string: string) {
2+
try {
3+
new URL(string);
4+
} catch (_) {
5+
return false;
6+
}
7+
8+
return true;
9+
}

src/utils/joinUrls.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { isAbsoluteUrl } from '.';
2+
3+
const withoutTrailingSlash = (url: string) => url.replace(/\/$/, '');
4+
const withoutLeadingSlash = (url: string) => url.replace(/^\//, '');
5+
6+
export function joinUrls(base: string | undefined, url: string | undefined): string {
7+
if (!base) {
8+
return url!;
9+
}
10+
if (!url) {
11+
return base;
12+
}
13+
14+
if (isAbsoluteUrl(url)) {
15+
return url;
16+
}
17+
18+
base = withoutTrailingSlash(base);
19+
url = withoutLeadingSlash(url);
20+
21+
return `${base}/${url}`;
22+
}

src/utils/joinsUrls.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { joinUrls } from './joinUrls';
2+
3+
test('correctly joins variations relative urls', () => {
4+
expect(joinUrls('/api/', '/banana')).toBe('/api/banana');
5+
expect(joinUrls('/api', '/banana')).toBe('/api/banana');
6+
7+
expect(joinUrls('/api/', 'banana')).toBe('/api/banana');
8+
expect(joinUrls('/api/', '/banana/')).toBe('/api/banana/');
9+
10+
expect(joinUrls('/', '/banana/')).toBe('/banana/');
11+
expect(joinUrls('/', 'banana/')).toBe('/banana/');
12+
13+
expect(joinUrls('/', '/banana')).toBe('/banana');
14+
expect(joinUrls('/', 'banana')).toBe('/banana');
15+
16+
expect(joinUrls('', '/banana')).toBe('/banana');
17+
expect(joinUrls('', 'banana')).toBe('banana');
18+
});
19+
20+
test('correctly joins variations of absolute urls', () => {
21+
expect(joinUrls('https://apple.com', '/api/banana/')).toBe('https://apple.com/api/banana/');
22+
expect(joinUrls('https://apple.com', '/api/banana')).toBe('https://apple.com/api/banana');
23+
24+
expect(joinUrls('https://apple.com/', 'api/banana/')).toBe('https://apple.com/api/banana/');
25+
expect(joinUrls('https://apple.com/', 'api/banana')).toBe('https://apple.com/api/banana');
26+
27+
expect(joinUrls('https://apple.com/', 'api/banana/')).toBe('https://apple.com/api/banana/');
28+
});

yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,9 +1102,9 @@
11021102
"@nodelib/fs.scandir" "2.1.3"
11031103
fastq "^1.6.0"
11041104

1105-
"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit":
1105+
"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit":
11061106
version "1.4.0"
1107-
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit#4e2beed60d6e564d2911d475d3d0055b07b914a1"
1107+
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit#4ef0bb45ceca425759da283a7de3377ef369f17c"
11081108
dependencies:
11091109
immer "^7.0.3"
11101110
redux "^4.0.0"

0 commit comments

Comments
 (0)