Skip to content

Commit 9e36af6

Browse files
msutkowskiShrugsy
andauthored
Support a custom paramsSerializer on fetchBaseQuery (#1594)
Co-authored-by: Josh Fraser <joshfraser91@gmail.com>
1 parent 06d6f12 commit 9e36af6

File tree

5 files changed

+100
-4
lines changed

5 files changed

+100
-4
lines changed

docs/rtk-query/api/fetchBaseQuery.mdx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ description: 'RTK Query > API: fetchBaseQuery reference'
1313

1414
This is a very small wrapper around [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) that aims to simplify requests. It is not a full-blown replacement for `axios`, `superagent`, or any other more heavy-weight library, but it will cover the large majority of your needs.
1515

16-
It takes all standard options from fetch's [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) interface, as well as `baseUrl`, a `prepareHeaders` function, and an optional `fetch` function.
16+
It takes all standard options from fetch's [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) interface, as well as `baseUrl`, a `prepareHeaders` function, an optional `fetch` function, and a `paramsSerializer` function.
1717

1818
- `baseUrl` _(required)_
1919
- Typically a string like `https://api.your-really-great-app.com/v1/`. If you don't provide a `baseUrl`, it defaults to a relative path from where the request is being made. You should most likely _always_ specify this.
@@ -25,6 +25,8 @@ It takes all standard options from fetch's [`RequestInit`](https://developer.moz
2525
;(headers: Headers, api: { getState: () => unknown }) => Headers
2626
```
2727

28+
- `paramsSerializer` _(optional)_
29+
- A function that can be used to apply custom transformations to the data passed into [`params`](#setting-the-query-string). If you don't provide this, `params` will be given directly to `new URLSearchParms()`. With some API integrations, you may need to leverage this to use something like the [`query-string`](https://github.com/sindresorhus/query-string) library to support different array types.
2830
- `fetchFn` _(optional)_
2931
- A fetch function that overrides the default on the window. Can be useful in SSR environments where you may need to leverage `isomorphic-fetch` or `cross-fetch`.
3032

@@ -154,15 +156,20 @@ By default, `fetchBaseQuery` assumes that every request you make will be `json`,
154156
155157
### Setting the query string
156158
157-
`fetchBaseQuery` provides a simple mechanism that converts an `object` to a serialized query string. If this doesn't suit your needs, you can always build your own querystring and set it in the `url`.
159+
`fetchBaseQuery` provides a simple mechanism that converts an `object` to a serialized query string by passing the object. If this doesn't suit your needs, you have two options:
160+
161+
1. Pass the `paramsSerializer` option to `fetchBaseQuery` to apply custom transformations
162+
2. Build your own querystring and set it in the `url`
158163
159164
```ts no-transpile
160165
// omitted
161166
endpoints: (builder) => ({
162167
updateUser: builder.query({
163168
query: (user: Record<string, string>) => ({
164169
url: `users`,
165-
params: user // The user object is automatically converted and produces a request like /api/users?first_name=test&last_name=example
170+
// Assuming no `paramsSerializer` is specified, the user object is automatically converted
171+
// and produces a url like /api/users?first_name=test&last_name=example
172+
params: user
166173
}),
167174
}),
168175
```

packages/toolkit/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"msw": "^0.28.2",
6767
"node-fetch": "^2.6.1",
6868
"prettier": "^2.2.1",
69+
"query-string": "^7.0.1",
6970
"rimraf": "^3.0.2",
7071
"rollup": "^2.47.0",
7172
"rollup-plugin-strip-code": "^0.2.6",

packages/toolkit/src/query/fetchBaseQuery.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export type FetchBaseQueryArgs = {
119119
input: RequestInfo,
120120
init?: RequestInit | undefined
121121
) => Promise<Response>
122+
paramsSerializer?: (params: Record<string, any>) => string
122123
} & RequestInit
123124

124125
export type FetchBaseQueryMeta = { request: Request; response?: Response }
@@ -156,11 +157,14 @@ export type FetchBaseQueryMeta = { request: Request; response?: Response }
156157
* Accepts a custom `fetch` function if you do not want to use the default on the window.
157158
* Useful in SSR environments if you need to use a library such as `isomorphic-fetch` or `cross-fetch`
158159
*
160+
* @param {(params: Record<string, unknown> => string} paramsSerializer
161+
* An optional function that can be used to stringify querystring parameters.
159162
*/
160163
export function fetchBaseQuery({
161164
baseUrl,
162165
prepareHeaders = (x) => x,
163166
fetchFn = defaultFetchFn,
167+
paramsSerializer,
164168
...baseFetchOptions
165169
}: FetchBaseQueryArgs = {}): BaseQueryFn<
166170
string | FetchArgs,
@@ -216,7 +220,9 @@ export function fetchBaseQuery({
216220

217221
if (params) {
218222
const divider = ~url.indexOf('?') ? '&' : '?'
219-
const query = new URLSearchParams(stripUndefined(params))
223+
const query = paramsSerializer
224+
? paramsSerializer(params)
225+
: new URLSearchParams(stripUndefined(params))
220226
url += divider + query
221227
}
222228

packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { setupApiStore } from './helpers'
44
import { server } from './mocks/server'
55
import { default as crossFetch } from 'cross-fetch'
66
import { rest } from 'msw'
7+
import queryString from 'query-string'
78

89
const defaultHeaders: Record<string, string> = {
910
fake: 'header',
@@ -450,6 +451,53 @@ describe('fetchBaseQuery', () => {
450451

451452
expect(request.url).toEqual(`${baseUrl}/echo?apple=fruit&randy=null`)
452453
})
454+
455+
it('should support a paramsSerializer', async () => {
456+
const baseQuery = fetchBaseQuery({
457+
baseUrl,
458+
fetchFn: fetchFn as any,
459+
paramsSerializer: (params: Record<string, unknown>) =>
460+
queryString.stringify(params, { arrayFormat: 'bracket' }),
461+
})
462+
463+
const api = createApi({
464+
baseQuery,
465+
endpoints(build) {
466+
return {
467+
query: build.query({
468+
query: () => ({ url: '/echo', headers: {} }),
469+
}),
470+
mutation: build.mutation({
471+
query: () => ({
472+
url: '/echo',
473+
method: 'POST',
474+
credentials: 'omit',
475+
}),
476+
}),
477+
}
478+
},
479+
})
480+
481+
const params = {
482+
someArray: ['a', 'b', 'c'],
483+
}
484+
485+
let request: any
486+
;({ data: request } = await baseQuery(
487+
{ url: '/echo', params },
488+
{
489+
signal: new AbortController().signal,
490+
dispatch: storeRef.store.dispatch,
491+
getState: storeRef.store.getState,
492+
extra: undefined,
493+
},
494+
{}
495+
))
496+
497+
expect(request.url).toEqual(
498+
`${baseUrl}/echo?someArray[]=a&someArray[]=b&someArray[]=c`
499+
)
500+
})
453501
})
454502

455503
describe('validateStatus', () => {

yarn.lock

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5043,6 +5043,7 @@ __metadata:
50435043
msw: ^0.28.2
50445044
node-fetch: ^2.6.1
50455045
prettier: ^2.2.1
5046+
query-string: ^7.0.1
50465047
redux: ^4.1.0
50475048
redux-thunk: ^2.3.0
50485049
reselect: ^4.0.0
@@ -12272,6 +12273,13 @@ __metadata:
1227212273
languageName: node
1227312274
linkType: hard
1227412275

12276+
"filter-obj@npm:^1.1.0":
12277+
version: 1.1.0
12278+
resolution: "filter-obj@npm:1.1.0"
12279+
checksum: 1049ac0c306198edf2c090375f7a80545a01e798b4996dbde13398a263cd923b5eb8a240dfadb03260621249a404a686e1a55980c6e59af70ee3880323998703
12280+
languageName: node
12281+
linkType: hard
12282+
1227512283
"finalhandler@npm:~1.1.2":
1227612284
version: 1.1.2
1227712285
resolution: "finalhandler@npm:1.1.2"
@@ -20152,6 +20160,18 @@ fsevents@^1.2.7:
2015220160
languageName: node
2015320161
linkType: hard
2015420162

20163+
"query-string@npm:^7.0.1":
20164+
version: 7.0.1
20165+
resolution: "query-string@npm:7.0.1"
20166+
dependencies:
20167+
decode-uri-component: ^0.2.0
20168+
filter-obj: ^1.1.0
20169+
split-on-first: ^1.0.0
20170+
strict-uri-encode: ^2.0.0
20171+
checksum: 0709d82a6dc332de978878b1bfe8de093b50527bcfcb2fd04e46f2d69c94bbad79e056a47e549b4ca5751cf624a03b27fb26ec3e29294a369e97be7d67279891
20172+
languageName: node
20173+
linkType: hard
20174+
2015520175
"querystring-es3@npm:^0.2.0":
2015620176
version: 0.2.1
2015720177
resolution: "querystring-es3@npm:0.2.1"
@@ -22657,6 +22677,13 @@ resolve@~1.19.0:
2265722677
languageName: node
2265822678
linkType: hard
2265922679

22680+
"split-on-first@npm:^1.0.0":
22681+
version: 1.1.0
22682+
resolution: "split-on-first@npm:1.1.0"
22683+
checksum: 2ef26fee62665be9547e8035734b856e658b08fd13e70271a2f258147f29d1f18e12b5cb7f7670d83e113c172a9c5fe3d87d9d7c02a1d3d57824818d75d942ab
22684+
languageName: node
22685+
linkType: hard
22686+
2266022687
"split-string@npm:^3.0.1, split-string@npm:^3.0.2":
2266122688
version: 3.1.0
2266222689
resolution: "split-string@npm:3.1.0"
@@ -22828,6 +22855,13 @@ resolve@~1.19.0:
2282822855
languageName: node
2282922856
linkType: hard
2283022857

22858+
"strict-uri-encode@npm:^2.0.0":
22859+
version: 2.0.0
22860+
resolution: "strict-uri-encode@npm:2.0.0"
22861+
checksum: 775012e88b9d8dff939d514bf376d615a15e8228a5dd587a94ac3c71fce41aa3635cd808aa796e2c1cd33f3f2fe2fbf89b74ee18a504a1905efa1854311e04bb
22862+
languageName: node
22863+
linkType: hard
22864+
2283122865
"string-argv@npm:~0.3.1":
2283222866
version: 0.3.1
2283322867
resolution: "string-argv@npm:0.3.1"

0 commit comments

Comments
 (0)