Skip to content
This repository has been archived by the owner on Jun 8, 2021. It is now read-only.

Commit

Permalink
Enhance/fetch base query (#7)
Browse files Browse the repository at this point in the history
- Enhances `fetchBaseQuery` to have a more familiar API
  • Loading branch information
themindoverall authored Nov 8, 2020
1 parent c65bc15 commit 8f84890
Show file tree
Hide file tree
Showing 17 changed files with 207 additions and 57 deletions.
2 changes: 1 addition & 1 deletion examples/posts-and-counter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"keywords": [],
"main": "./index.html",
"dependencies": {
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit",
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit",
"@rtk-incubator/simple-query": "https://pkg.csb.dev/rtk-incubator/simple-query/commit/fcea624c/@rtk-incubator/simple-query",
"msw": "0.21.3",
"react": "17.0.0",
Expand Down
4 changes: 2 additions & 2 deletions examples/posts-and-counter/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1369,9 +1369,9 @@
resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca"
integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==

"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit":
"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit":
version "1.4.0"
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit#4e2beed60d6e564d2911d475d3d0055b07b914a1"
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit#4ef0bb45ceca425759da283a7de3377ef369f17c"
dependencies:
immer "^7.0.3"
redux "^4.0.0"
Expand Down
2 changes: 1 addition & 1 deletion examples/svelte-counter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"typescript": "^3.9.3"
},
"dependencies": {
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit",
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit",
"sirv-cli": "^1.0.0"
}
}
2 changes: 1 addition & 1 deletion examples/svelte-counter/sandbox.config.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"template": "node"
}
}
4 changes: 4 additions & 0 deletions examples/svelte-counter/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

onMount(async () => {
({ refetch: getCount } = store.dispatch(counterApi.queryActions.getCount()));
store.dispatch(counterApi.queryActions.getAbsoluteTest())
store.dispatch(counterApi.queryActions.getError());
store.dispatch(counterApi.queryActions.getNetworkError());
store.dispatch(counterApi.queryActions.getHeaderError());
});
</script>

Expand Down
22 changes: 22 additions & 0 deletions examples/svelte-counter/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ let count = 0;
let counters = {};

export const handlers = [
rest.get('/error', (req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({
message: 'what is this doing!',
data: [{ some: 'key' }],
}),
);
}),
rest.get('/network-error', (req, res, ctx) => {
return res.networkError('Fake network error');
}),
rest.get('/mismatched-header-error', (req, res, ctx) => {
return res(ctx.text('oh hello there'), ctx.set('Content-Type', 'application/hal+banana'));
}),
rest.get('https://mocked.data', (req, res, ctx) => {
return res(
ctx.json({
great: 'success',
}),
);
}),
rest.put<{ amount: number }>('/increment', (req, res, ctx) => {
const { amount } = req.body;
count = count += amount;
Expand Down
81 changes: 49 additions & 32 deletions examples/svelte-counter/src/services/counter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,72 @@ interface CountResponse {

export const counterApi = createApi({
reducerPath: 'counterApi',
baseQuery: fetchBaseQuery(),
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
entityTypes: ['Counter'],
endpoints: (build) => ({
getError: build.query({
query: (_: void) => '/error',
}),
getNetworkError: build.query({
query: (_: void) => '/network-error',
}),
getHeaderError: build.query({
query: (_: void) => '/mismatched-header-error',
}),
getAbsoluteTest: build.query<any, void>({
query: () => ({
url: 'https://mocked.data',
params: {
hello: 'friend',
},
}),
}),
getCount: build.query<CountResponse, void>({
query: () => 'count',
query: () => ({
url: `/count?=${'whydothis'}`,
params: {
test: 'param',
additional: 1,
},
}),
provides: ['Counter'],
}),
getCountById: build.query<CountResponse, number>({
query: (id: number) => `${id}`,
provides: (_, id) => [{ type: 'Counter', id }],
}),
incrementCount: build.mutation<CountResponse, number>({
query(amount) {
return {
url: `increment`,
query: (amount) => ({
url: `/increment`,
method: 'PUT',
body: { amount },
}),
invalidates: ['Counter'],
}),
decrementCount: build.mutation<CountResponse, number>({
query: (amount) => ({
url: `decrement`,
method: 'PUT',
body: JSON.stringify({ amount }),
};
},
body: { amount },
}),
invalidates: ['Counter'],
}),
getCountById: build.query<CountResponse, number>({
query: (id: number) => `${id}`,
provides: (_, id) => [{ type: 'Counter', id }],
}),
incrementCountById: build.mutation<CountResponse, { id: number; amount: number }>({
query({ id, amount }) {
return {
query: ({ id, amount }) => ({
url: `${id}/increment`,
method: 'PUT',
body: JSON.stringify({ amount }),
};
},
body: { amount },
}),
invalidates: (_, { id }) => [{ type: 'Counter', id }],
}),
decrementCount: build.mutation<CountResponse, number>({
query(amount) {
return {
url: `decrement`,
method: 'PUT',
body: JSON.stringify({ amount }),
};
},
invalidates: ['Counter'],
}),
decrementCountById: build.mutation<CountResponse, { id: number; amount: number }>({
query({ id, amount }) {
return {
query: ({ id, amount }) => ({
url: `${id}/decrement`,
method: 'PUT',
body: JSON.stringify({ amount }),
};
},
body: { amount },
}),
invalidates: (_, { id }) => [{ type: 'Counter', id }],
}),
}),
Expand Down
5 changes: 2 additions & 3 deletions examples/svelte-counter/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71"
integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==

"@reduxjs/toolkit@^1.4.0":
"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.4.0.tgz#ee2e2384cc3d1d76780d844b9c2da3580d32710d"
integrity sha512-hkxQwVx4BNVRsYdxjNF6cAseRmtrkpSlcgJRr3kLUcHPIAMZAmMJkXmHh/eUEGTMqPzsYpJLM7NN2w9fxQDuGw==
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit#4ef0bb45ceca425759da283a7de3377ef369f17c"
dependencies:
immer "^7.0.3"
redux "^4.0.0"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"react-redux": "^7.2.1"
},
"devDependencies": {
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit",
"@reduxjs/toolkit": "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit",
"@size-limit/preset-small-lib": "^4.6.0",
"@testing-library/react": "^11.1.0",
"@testing-library/react-hooks": "^3.4.2",
Expand Down
2 changes: 1 addition & 1 deletion src/buildActionMaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ function assertIsNewRTKPromise(action: ReturnType<ThunkAction<any, any, any, any
You are running a version of RTK that is too old.
Currently you need an experimental build of RTK.
Please install it via
yarn add "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit"
yarn add "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit"
`);
}
}
63 changes: 50 additions & 13 deletions src/fetchBaseQuery.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,65 @@
import { QueryApi } from './buildThunks';
import { joinUrls } from './utils';
import { isPlainObject } from '@reduxjs/toolkit';

interface FetchArgs extends RequestInit {
url: string;
params?: Record<string, any>;
body?: any;
responseHandler?: 'json' | 'text' | ((response: Response) => Promise<any>);
validateStatus?: (response: Response, body: any) => boolean;
}

export function fetchBaseQuery({ baseUrl }: { baseUrl: string } = { baseUrl: '' }) {
const defaultValidateStatus = (response: Response) => response.status >= 200 && response.status <= 299;

const isJsonContentType = (headers: Headers) => headers.get('content-type')?.trim()?.startsWith('application/json');

export function fetchBaseQuery({ baseUrl }: { baseUrl?: string } = {}) {
return async (arg: string | FetchArgs, { signal, rejectWithValue }: QueryApi) => {
const { url, method = 'GET', ...rest } = typeof arg == 'string' ? { url: arg } : arg;
const result = await fetch(`${baseUrl}/${url}`, {
let {
url,
method = 'GET' as const,
headers = undefined,
body = undefined,
params = undefined,
responseHandler = 'json' as const,
validateStatus = defaultValidateStatus,
...rest
} = typeof arg == 'string' ? { url: arg } : arg;
let config: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
},
signal,
body,
...rest,
});
};

config.headers = new Headers(headers);

if (!config.headers.has('content-type')) {
config.headers.set('content-type', 'application/json');
}

if (body && isPlainObject(body) && isJsonContentType(config.headers)) {
config.body = JSON.stringify(body);
}

if (params) {
const divider = ~url.indexOf('?') ? '&' : '?';
const query = new URLSearchParams(params);
url += divider + query;
}

url = joinUrls(baseUrl, url);

const response = await fetch(url, config);

let resultData =
result.headers.has('Content-Type') && !result.headers.get('Content-Type')?.trim()?.startsWith('application/json')
? await result.text()
: await result.json();
const resultData =
typeof responseHandler === 'function'
? await responseHandler(response)
: await response[responseHandler || 'text']();

return result.status >= 200 && result.status <= 299
return validateStatus(response, resultData)
? resultData
: rejectWithValue({ status: result.status, data: resultData });
: rejectWithValue({ status: response.status, data: resultData });
};
}
3 changes: 3 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './isAbsoluteUrl';
export * from './isValidUrl';
export * from './joinUrls';
9 changes: 9 additions & 0 deletions src/utils/isAbsoluteUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* If either :// or // is present consider it to be an absolute url
*
* @param url string
*/

export function isAbsoluteUrl(url: string) {
return new RegExp(`(^|:)//`).test(url);
}
9 changes: 9 additions & 0 deletions src/utils/isValidUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function isValidUrl(string: string) {
try {
new URL(string);
} catch (_) {
return false;
}

return true;
}
22 changes: 22 additions & 0 deletions src/utils/joinUrls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { isAbsoluteUrl } from '.';

const withoutTrailingSlash = (url: string) => url.replace(/\/$/, '');
const withoutLeadingSlash = (url: string) => url.replace(/^\//, '');

export function joinUrls(base: string | undefined, url: string | undefined): string {
if (!base) {
return url!;
}
if (!url) {
return base;
}

if (isAbsoluteUrl(url)) {
return url;
}

base = withoutTrailingSlash(base);
url = withoutLeadingSlash(url);

return `${base}/${url}`;
}
28 changes: 28 additions & 0 deletions src/utils/joinsUrls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { joinUrls } from './joinUrls';

test('correctly joins variations relative urls', () => {
expect(joinUrls('/api/', '/banana')).toBe('/api/banana');
expect(joinUrls('/api', '/banana')).toBe('/api/banana');

expect(joinUrls('/api/', 'banana')).toBe('/api/banana');
expect(joinUrls('/api/', '/banana/')).toBe('/api/banana/');

expect(joinUrls('/', '/banana/')).toBe('/banana/');
expect(joinUrls('/', 'banana/')).toBe('/banana/');

expect(joinUrls('/', '/banana')).toBe('/banana');
expect(joinUrls('/', 'banana')).toBe('/banana');

expect(joinUrls('', '/banana')).toBe('/banana');
expect(joinUrls('', 'banana')).toBe('banana');
});

test('correctly joins variations of absolute urls', () => {
expect(joinUrls('https://apple.com', '/api/banana/')).toBe('https://apple.com/api/banana/');
expect(joinUrls('https://apple.com', '/api/banana')).toBe('https://apple.com/api/banana');

expect(joinUrls('https://apple.com/', 'api/banana/')).toBe('https://apple.com/api/banana/');
expect(joinUrls('https://apple.com/', 'api/banana')).toBe('https://apple.com/api/banana');

expect(joinUrls('https://apple.com/', 'api/banana/')).toBe('https://apple.com/api/banana/');
});
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1102,9 +1102,9 @@
"@nodelib/fs.scandir" "2.1.3"
fastq "^1.6.0"

"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit":
"@reduxjs/toolkit@https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit":
version "1.4.0"
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/56994225/@reduxjs/toolkit#4e2beed60d6e564d2911d475d3d0055b07b914a1"
resolved "https://pkg.csb.dev/reduxjs/redux-toolkit/commit/2c869f4d/@reduxjs/toolkit#4ef0bb45ceca425759da283a7de3377ef369f17c"
dependencies:
immer "^7.0.3"
redux "^4.0.0"
Expand Down

0 comments on commit 8f84890

Please sign in to comment.