Skip to content

Commit 1eaf90a

Browse files
author
Francesco Benedetto
authored
Provide original request in response interceptor (#319) (#344)
* Provide original request in response interceptor (#319) * Update README adding request to response interceptor * Add test to cover request as part of response interceptor parameters
1 parent 0832cdd commit 1eaf90a

File tree

4 files changed

+61
-6
lines changed

4 files changed

+61
-6
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ function App() {
602602
return options
603603
},
604604
// every time we make an http request, before getting the response back, this will run
605-
response: async ({ response }) => {
605+
response: async ({ response, request }) => {
606606
// unfortunately, because this is a JS Response object, we have to modify it directly.
607607
// It shouldn't have any negative affect since this is getting reset on each request.
608608
const res = response
@@ -822,8 +822,10 @@ const options = {
822822
request: async ({ options, url, path, route }) => { // `async` is not required
823823
return options // returning the `options` is important
824824
},
825-
response: async ({ response }) => {
826-
// note: `response.data` is equivalent to `await response.json()`
825+
response: async ({ response, request }) => {
826+
// notes:
827+
// - `response.data` is equivalent to `await response.json()`
828+
// - `request` is an object matching the standard fetch's options
827829
return response // returning the `response` is important
828830
}
829831
},

src/__tests__/useFetch.test.tsx

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { toCamel } from 'convert-keys'
1010
import { renderHook, act } from '@testing-library/react-hooks'
1111
import mockConsole from 'jest-mock-console'
1212
import * as mockdate from 'mockdate'
13-
import defaults from '../defaults'
13+
import defaults, { useFetchArgsDefaults } from '../defaults'
1414

1515
import { Res, IncomingOptions, CachePolicies } from '../types'
1616
import { emptyCustomResponse, sleep, makeError, addSlash } from '../utils'
@@ -581,10 +581,25 @@ describe('useFetch - BROWSER - interceptors', (): void => {
581581
)
582582
}
583583

584+
const response = jest.fn(({ response: res }) => res)
585+
const mockResInterceptorWrapper = ({ children }: { children?: ReactNode }): ReactElement => {
586+
const options: IncomingOptions = {
587+
interceptors: {
588+
request,
589+
response
590+
},
591+
cachePolicy: NO_CACHE
592+
}
593+
return (
594+
<Provider url='https://example.com' options={options}>{children}</Provider>
595+
)
596+
}
597+
584598
afterEach((): void => {
585599
fetch.resetMocks()
586600
cleanup()
587601
request.mockClear()
602+
response.mockClear()
588603
})
589604

590605
beforeEach((): void => {
@@ -643,6 +658,44 @@ describe('useFetch - BROWSER - interceptors', (): void => {
643658
expect(request.mock.calls[0][0].url).toBe('https://example.com')
644659
})
645660

661+
it('should pass the proper request to `interceptors.response`', async (): Promise<void> => {
662+
const { result } = renderHook(
663+
() => useFetch(),
664+
{ wrapper: mockResInterceptorWrapper }
665+
)
666+
await act(result.current.get)
667+
expect(fetch.mock.calls[0][0]).toBe('https://example.com')
668+
expect(request.mock.calls[0][0].url).toBe('https://example.com')
669+
expect(response.mock.calls[0][0].request).toStrictEqual(useFetchArgsDefaults.requestInit)
670+
})
671+
672+
it('should pass custom request options to `interceptors.response`', async (): Promise<void> => {
673+
const customReqOptions: RequestInit = {
674+
headers: {
675+
Authorization: 'Bearer TOKEN'
676+
},
677+
credentials: 'include',
678+
cache: 'no-store'
679+
}
680+
const { result } = renderHook(
681+
() => useFetch('https://custom-url.com', customReqOptions),
682+
{ wrapper: mockResInterceptorWrapper }
683+
)
684+
await act(result.current.get)
685+
expect(fetch.mock.calls[0][0]).toBe('https://custom-url.com')
686+
expect(request.mock.calls[0][0].url).toBe('https://custom-url.com')
687+
688+
const expectedOpts = {
689+
headers: {
690+
...useFetchArgsDefaults.requestInit.headers,
691+
...customReqOptions.headers
692+
},
693+
credentials: customReqOptions.credentials,
694+
cache: customReqOptions.cache
695+
}
696+
expect(response.mock.calls[0][0].request).toStrictEqual(expectedOpts)
697+
})
698+
646699
it('should still call both interceptors when using cache', async (): Promise<void> => {
647700
let requestCalled = 0
648701
let responseCalled = 0

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export type UseFetch<TData> = UseFetchArrayReturn<TData> &
163163

164164
export type Interceptors<TData = any> = {
165165
request?: ({ options, url, path, route }: { options: RequestInit, url?: string, path?: string, route?: string }) => Promise<RequestInit> | RequestInit
166-
response?: ({ response }: { response: Res<TData> }) => Promise<Res<TData>>
166+
response?: ({ response }: { response: Res<TData>, request: RequestInit }) => Promise<Res<TData>>
167167
}
168168

169169
// this also holds the response keys. It mimics js Map

src/useFetch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ function useFetch<TData = any>(...args: UseFetchArgs): UseFetch<TData> {
112112
newData = await tryGetData(newRes, defaults.data, responseType)
113113
res.current.data = onNewData(data.current, newData)
114114

115-
res.current = interceptors.response ? await interceptors.response({ response: res.current }) : res.current
115+
res.current = interceptors.response ? await interceptors.response({ response: res.current, request: requestInit }) : res.current
116116
invariant('data' in res.current, 'You must have `data` field on the Response returned from your `interceptors.response`')
117117
data.current = res.current.data as TData
118118

0 commit comments

Comments
 (0)