Skip to content

Commit c6b4d7c

Browse files
authored
Fixes/changes (#234)
* v0.4.4 * v0.4.5 * reorganized and fixed tests * cool * gonna do github package later bc it requires @org/repo and I dont wanna do @org part now * weird caching issue with Promise.all fixed, [request] as dep causing infinite loop fix, [response] must be how we do deps -- cannot do [response.ok] * fixing it so [request, response] dont cause infinite loops and extra rereners in useEffect
1 parent 4fdd49e commit c6b4d7c

11 files changed

+171
-113
lines changed

README.md

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,7 @@ Todos
964964
- .json() ['application/json']
965965
- .text() ['text/plain']
966966
- .blob() ['image/png', 'application/octet-stream']
967+
- [ ] is making a [gitpod](https://www.gitpod.io/docs/configuration/) useful here? 🤔
967968
- [ ] suspense
968969
- [ ] triggering it from outside the `<Suspense />` component.
969970
- add `.read()` to `request`
@@ -997,21 +998,6 @@ Todos
997998
- [ ] show comparison with Apollo
998999
- [ ] figure out a good way to show side-by-side comparisons
9991000
- [ ] show comparison with Axios
1000-
- [ ] maybe add syntax for middle helpers for inline `headers` or `queries` like this:
1001-
1002-
```jsx
1003-
const request = useFetch('https://example.com')
1004-
1005-
request
1006-
.headers({
1007-
auth: jwt // this would inline add the `auth` header
1008-
})
1009-
.query({ // might have to use .params({ }) since we're using .query() for GraphQL
1010-
no: 'way' // this would inline make the url: https://example.com?no=way
1011-
})
1012-
.get()
1013-
```
1014-
10151001
- [ ] potential option ideas
10161002
10171003
```jsx
@@ -1021,6 +1007,14 @@ Todos
10211007
// to overwrite those of `useFetch` for
10221008
// `useMutation` and `useQuery`
10231009
},
1010+
responseType: 'json', // similar to axios
1011+
// OR can be an array. We will try to get the `data`
1012+
// by attempting to extract it via these body interface
1013+
// methods, one by one in this order
1014+
responseType: ['json', 'text', 'blob', 'formData', 'arrayBuffer'],
1015+
// ALSO, maybe there's a way to guess the proper `body interface method` for the correct response content-type.
1016+
// here's a stackoverflow with someone who's tried: https://bit.ly/2X8iaVG
1017+
10241018
// Allows you to pass in your own cache to useFetch
10251019
// This is controversial though because `cache` is an option in the requestInit
10261020
// and it's value is a string. See: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "use-http",
3-
"version": "0.4.4",
3+
"version": "0.4.5",
44
"homepage": "http://use-http.com",
55
"main": "dist/index.js",
66
"license": "MIT",

src/__tests__/doFetchArgs.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import doFetchArgs from '../doFetchArgs'
22
import { HTTPMethod } from '../types'
3-
import { defaults } from '../useFetchArgs'
3+
import defaults from '../defaults'
44
import useCache from '../useCache'
55

66
describe('doFetchArgs: general usages', (): void => {

src/__tests__/useFetch.test.tsx

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
/* eslint-disable no-var */
22
/* eslint-disable camelcase */
33
/* eslint-disable @typescript-eslint/camelcase */
4-
import React, { ReactElement, ReactNode } from 'react'
4+
import React, { ReactElement, ReactNode, useEffect } from 'react'
55
import { useFetch, Provider } from '..'
66
import { cleanup } from '@testing-library/react'
7+
import * as test from '@testing-library/react'
78
import { FetchMock } from 'jest-fetch-mock'
89
import { toCamel } from 'convert-keys'
910
import { renderHook, act } from '@testing-library/react-hooks'
1011
import mockConsole from 'jest-mock-console'
1112
import * as mockdate from 'mockdate'
13+
import defaults from '../defaults'
1214

1315
import { Res, Options, CachePolicies } from '../types'
1416
import { emptyCustomResponse, sleep, makeError } from '../utils'
@@ -92,11 +94,42 @@ describe('useFetch - BROWSER - basic functionality', (): void => {
9294
var formData = new FormData()
9395
formData.append('username', 'AlexCory')
9496
await result.current.post(formData)
95-
const options = fetch.mock.calls[0][1] || {}
97+
const options = fetch.mock.calls[0][1] || { headers: {} }
9698
expect(options.method).toBe('POST')
97-
expect(options.headers).toBeUndefined()
99+
expect('Content-Type' in (options as any).headers).toBe(false)
98100
})
99101
})
102+
103+
it('should not cause infinite loop with `[request]` as dependency', async () => {
104+
function Section() {
105+
const { request, data } = useFetch('https://a.co')
106+
useEffect(() => {
107+
request.get()
108+
}, [request])
109+
return <div>{JSON.stringify(data)}</div>
110+
}
111+
const { container } = test.render(<Section />)
112+
113+
await test.act(async (): Promise<any> => await sleep(100))
114+
expect(JSON.parse(container.textContent as string)).toEqual(expected)
115+
})
116+
117+
it('should not cause infinite loop with `[response]` as dependency', async () => {
118+
function Section() {
119+
const { request, response, data } = useFetch('https://a.co')
120+
useEffect(() => {
121+
(async () => {
122+
await request.get()
123+
if (!response.ok) console.error('no okay')
124+
})()
125+
}, [request, response])
126+
return <div>{JSON.stringify(data)}</div>
127+
}
128+
const { container } = test.render(<Section />)
129+
130+
await test.act(async (): Promise<any> => await sleep(100))
131+
expect(JSON.parse(container.textContent as string)).toEqual(expected)
132+
})
100133
})
101134

102135
describe('useFetch - BROWSER - with <Provider />', (): void => {
@@ -591,8 +624,9 @@ describe('useFetch - BROWSER - Overwrite Global Options set in Provider', (): vo
591624
})
592625

593626
it('should only add Content-Type: application/json for POST and PUT by default', async (): Promise<void> => {
594-
const expectedHeadersGET = providerHeaders
627+
const expectedHeadersGET = { ...defaults.headers, ...providerHeaders }
595628
const expectedHeadersPOSTandPUT = {
629+
...defaults.headers,
596630
...providerHeaders,
597631
'Content-Type': 'application/json'
598632
}
@@ -613,7 +647,10 @@ describe('useFetch - BROWSER - Overwrite Global Options set in Provider', (): vo
613647
})
614648

615649
it('should have the correct headers set in the options set in the Provider', async (): Promise<void> => {
616-
const expectedHeaders = providerHeaders
650+
const expectedHeaders = {
651+
...defaults.headers,
652+
...providerHeaders
653+
}
617654
const { result } = renderHook(
618655
() => useFetch(),
619656
{ wrapper }
@@ -625,7 +662,7 @@ describe('useFetch - BROWSER - Overwrite Global Options set in Provider', (): vo
625662
})
626663

627664
it('should overwrite url and options set in the Provider', async (): Promise<void> => {
628-
const expectedHeaders = undefined
665+
const expectedHeaders = defaults.headers
629666
const expectedURL = 'https://example2.com'
630667
const { result, waitForNextUpdate } = renderHook(
631668
() => useFetch(expectedURL, globalOptions => {
@@ -644,7 +681,7 @@ describe('useFetch - BROWSER - Overwrite Global Options set in Provider', (): vo
644681
})
645682

646683
it('should overwrite options set in the Provider', async (): Promise<void> => {
647-
const expectedHeaders = undefined
684+
const expectedHeaders = defaults.headers
648685
const { result, waitForNextUpdate } = renderHook(
649686
() => useFetch(globalOptions => {
650687
// TODO: fix the generics here so it knows when a header

src/__tests__/useFetchArgs.test.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { renderHook } from '@testing-library/react-hooks'
2-
import useFetchArgs, { useFetchArgsDefaults } from '../useFetchArgs'
2+
import useFetchArgs from '../useFetchArgs'
3+
import defaults, { useFetchArgsDefaults } from '../defaults'
34
import React, { ReactElement, ReactNode } from 'react'
45
import { Provider } from '..'
56

@@ -183,7 +184,11 @@ describe('useFetchArgs: general usages', (): void => {
183184
url: 'https://example.com'
184185
},
185186
requestInit: {
186-
...options
187+
...options,
188+
headers: {
189+
...defaults.headers,
190+
...options.headers
191+
}
187192
}
188193
})
189194
})
@@ -201,7 +206,10 @@ describe('useFetchArgs: general usages', (): void => {
201206
url: 'http://localhost'
202207
},
203208
requestInit: {
204-
...options
209+
headers: {
210+
...defaults.headers,
211+
...options.headers
212+
}
205213
}
206214
})
207215
})
@@ -231,7 +239,11 @@ describe('useFetchArgs: general usages', (): void => {
231239
url: 'http://localhost'
232240
},
233241
requestInit: {
234-
...overwriteProviderOptions
242+
...overwriteProviderOptions,
243+
headers: {
244+
...defaults.headers,
245+
...overwriteProviderOptions.headers
246+
}
235247
}
236248
})
237249
})

src/defaults.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Flatten, CachePolicies, UseFetchArgsReturn } from './types'
2+
import { isObject } from './utils'
3+
4+
5+
export const useFetchArgsDefaults: UseFetchArgsReturn = {
6+
customOptions: {
7+
cacheLife: 0,
8+
cachePolicy: CachePolicies.CACHE_FIRST,
9+
interceptors: {},
10+
onAbort: () => { /* do nothing */ },
11+
onNewData: (currData: any, newData: any) => newData,
12+
onTimeout: () => { /* do nothing */ },
13+
path: '',
14+
perPage: 0,
15+
persist: false,
16+
retries: 0,
17+
retryDelay: 1000,
18+
retryOn: [],
19+
suspense: false,
20+
timeout: 0,
21+
url: '',
22+
},
23+
requestInit: {
24+
headers: {
25+
Accept: 'application/json, text/plain, */*'
26+
}
27+
},
28+
defaults: {
29+
data: undefined,
30+
loading: false
31+
},
32+
dependencies: undefined
33+
}
34+
35+
export default Object.entries(useFetchArgsDefaults).reduce((acc, [key, value]) => {
36+
if (isObject(value)) return { ...acc, ...value }
37+
return { ...acc, [key]: value }
38+
}, {} as Flatten<UseFetchArgsReturn>)

src/doFetchArgs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ export default async function doFetchArgs<TData = any>(
4444
((bodyAs2ndParam as any) instanceof FormData ||
4545
(bodyAs2ndParam as any) instanceof URLSearchParams)
4646
) return bodyAs2ndParam as any
47-
if (isBodyObject(bodyAs2ndParam)) return JSON.stringify(bodyAs2ndParam)
48-
if (isBodyObject(initialOptions.body)) return JSON.stringify(initialOptions.body)
47+
if (isBodyObject(bodyAs2ndParam) || isString(bodyAs2ndParam)) return JSON.stringify(bodyAs2ndParam)
48+
if (isBodyObject(initialOptions.body) || isString(bodyAs2ndParam)) return JSON.stringify(initialOptions.body)
4949
return null
5050
})()
5151

src/types.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,32 @@ export type OverwriteGlobalOptions = (options: Options) => Options
206206
export type RetryOn = (<TData = any>({ attempt, error, response }: { attempt: number, error: Error, response: Res<TData> | null }) => boolean) | number[]
207207
export type RetryDelay = (<TData = any>({ attempt, error, response }: { attempt: number, error: Error, response: Res<TData> | null }) => number) | number
208208

209+
export type UseFetchArgsReturn = {
210+
customOptions: {
211+
cacheLife: number
212+
cachePolicy: CachePolicies
213+
interceptors: Interceptors
214+
onAbort: () => void
215+
onNewData: (currData: any, newData: any) => any
216+
onTimeout: () => void
217+
path: string
218+
perPage: number
219+
persist: boolean
220+
retries: number
221+
retryDelay: RetryDelay
222+
retryOn: RetryOn | undefined
223+
suspense: boolean
224+
timeout: number
225+
url: string
226+
}
227+
requestInit: RequestInit
228+
defaults: {
229+
loading: boolean
230+
data?: any
231+
}
232+
dependencies?: any[]
233+
}
234+
209235
/**
210236
* Helpers
211237
*/

0 commit comments

Comments
 (0)