Skip to content

Commit 426745c

Browse files
committed
Add useTriggerableFetch
1 parent 16a867e commit 426745c

9 files changed

+136
-9
lines changed

README.md

+25
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@ const Dogs = () => {
5757
}
5858
```
5959

60+
## useTriggerableFetch
61+
62+
```javascript
63+
import { useTriggerableFetch } from 'react-fetch-json-hook'
64+
65+
const Dogs = () => {
66+
const { trigger, error, loading } = useTriggerableFetch('/api/dogs')
67+
68+
if (loading) {
69+
return <div>Loading...</div>
70+
}
71+
72+
if (error) {
73+
return <div>Error! {error.message}</div>
74+
}
75+
76+
return (
77+
<ul>
78+
<button onClick={trigger}>Fetch Dogs</button>
79+
{data && data.dogs.map(dog => <li key={dog.id}>{dog.breed}</li>)}
80+
</ul>
81+
)
82+
}
83+
```
84+
6085
## Authorization
6186

6287
```javascript

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-fetch-json-hook",
3-
"version": "0.1.3",
3+
"version": "0.1.4",
44
"main": "lib/index.js",
55
"module": "es/index.js",
66
"typings": "lib/index.d.ts",

src/FetchConnectorContext.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import React from 'react'
2+
import { FetchConnector } from './fetch-connector'
3+
4+
export default React.createContext<FetchConnector | null>(null)

src/FetchHookProvider.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22
import { FetchConnector } from './fetch-connector'
3-
import { FetchConnectorContext } from './use-fetch'
3+
import FetchConnectorContext from './FetchConnectorContext'
44

55
interface FetchHookProviderProps {
66
readonly connector: FetchConnector

src/__tests__/use-fetch.test.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { renderHook } from 'react-hooks-testing-library'
33
import mockedResponse from '../__mocks__/mocked-response.json'
44
import { createFetchConnector, FetchConnector } from '../fetch-connector'
55
import fetchJSON from '../fetch-json'
6-
import useFetch, { FetchConnectorContext } from '../use-fetch'
6+
import FetchConnectorContext from '../FetchConnectorContext'
7+
import useFetch from '../use-fetch'
78

89
jest.mock('../internal/actHack')
910
jest.mock('../fetch-json')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from 'react'
2+
import { renderHook } from 'react-hooks-testing-library'
3+
import mockedResponse from '../__mocks__/mocked-response.json'
4+
import { createFetchConnector, FetchConnector } from '../fetch-connector'
5+
import fetchJSON from '../fetch-json'
6+
import FetchConnectorContext from '../FetchConnectorContext'
7+
import useTriggerableFetch from '../use-triggerable-fetch'
8+
9+
jest.mock('../internal/actHack')
10+
jest.mock('../fetch-json')
11+
12+
const mockedFetchJSON = (fetchJSON as unknown) as jest.Mock<typeof fetchJSON>
13+
14+
const createWrapperComponent = (connector: FetchConnector) => {
15+
const WrapperComponent: React.FC<{ children?: React.ReactNode }> = ({
16+
children,
17+
}) => (
18+
<FetchConnectorContext.Provider value={connector}>
19+
{children}
20+
</FetchConnectorContext.Provider>
21+
)
22+
23+
return WrapperComponent
24+
}
25+
26+
describe('UseTriggerableFetchHook', () => {
27+
test('should fetch without error', async () => {
28+
const connector = createFetchConnector()
29+
const hook = renderHook(() => useTriggerableFetch('/foo'), {
30+
wrapper: createWrapperComponent(connector),
31+
})
32+
33+
expect(hook.result.current.error).toEqual(undefined)
34+
expect(hook.result.current.data).toEqual(undefined)
35+
expect(typeof hook.result.current.trigger).toEqual('function')
36+
expect(hook.result.current.loading).toEqual(false)
37+
hook.result.current.trigger()
38+
expect(hook.result.current.error).toEqual(undefined)
39+
expect(hook.result.current.data).toEqual(undefined)
40+
expect(typeof hook.result.current.trigger).toEqual('function')
41+
expect(hook.result.current.loading).toEqual(true)
42+
await hook.waitForNextUpdate()
43+
expect(hook.result.current.error).toEqual(undefined)
44+
expect(hook.result.current.data).toEqual(mockedResponse['/foo'])
45+
expect(typeof hook.result.current.trigger).toEqual('function')
46+
expect(hook.result.current.loading).toEqual(false)
47+
expect(mockedFetchJSON.mock.calls.length).toBe(1)
48+
})
49+
})

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export { default as useFetch } from './use-fetch'
22
export { default as FetchHookProvider } from './FetchHookProvider'
3+
export { default as FetchConnectorContext } from './FetchConnectorContext'
4+
export { default as useTriggerableFetch } from './use-triggerable-fetch'
35
export { default as getMarkupFromTree } from './get-markup-from-tree'
46
export * from './fetch-connector'
57
export * from './internal/ssr-context'

src/use-fetch.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ import {
55
registerItemToFetchConnector,
66
uncacheQueries,
77
} from './fetch-connector'
8-
import actHack from './internal/actHack'
8+
import fetchConnectorContext from './FetchConnectorContext'
99
import { FetchSSRManagerContext } from './get-markup-from-tree'
10-
11-
export const FetchConnectorContext = React.createContext<FetchConnector | null>(
12-
null,
13-
)
10+
import actHack from './internal/actHack'
1411

1512
export interface FetchMorePlan<Payload, NextPayload> extends RequestInit {
1613
uri: string
@@ -85,7 +82,7 @@ export default function useFetch<Payload>(
8582
options?: UseFetchOptions,
8683
): FetchHookResult<Payload> {
8784
const { stackSize = 3 } = options || {}
88-
const fetchConnector = useContext(FetchConnectorContext)
85+
const fetchConnector = useContext(fetchConnectorContext)
8986
const ssrManager = useContext(FetchSSRManagerContext)
9087
const queryId = useMemo(() => createQueryCacheId(uri, options), [
9188
uri,

src/use-triggerable-fetch.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { useCallback, useContext, useMemo, useState } from 'react'
2+
import fetchJSON from './fetch-json'
3+
import FetchConnectorContext from './FetchConnectorContext'
4+
import actHack from './internal/actHack'
5+
6+
interface MutationFetchHookState<Payload> {
7+
data?: Payload
8+
loading: boolean
9+
error?: Error
10+
}
11+
12+
export default function useTriggerableFetch<Payload>(url: string) {
13+
const fetchConnector = useContext(FetchConnectorContext)
14+
15+
if (!fetchConnector) {
16+
throw new Error(
17+
'useFetch should not be used outside <FetchConnectorContext.Provider />',
18+
)
19+
}
20+
21+
const [state, setState] = useState<MutationFetchHookState<Payload>>({
22+
loading: false,
23+
})
24+
const trigger = useCallback(
25+
async (body?: any, options?: RequestInit) => {
26+
actHack(() => setState({ loading: true }))
27+
try {
28+
const data: Payload = await fetchJSON(url, {
29+
...options,
30+
headers: {
31+
...fetchConnector.requestHeaders,
32+
...(options && options.headers),
33+
},
34+
body,
35+
})
36+
actHack(() => setState({ data, loading: false }))
37+
38+
return data
39+
} catch (error) {
40+
actHack(() => setState({ error, loading: false }))
41+
return error
42+
}
43+
},
44+
[url, setState],
45+
)
46+
const result = useMemo(() => ({ ...state, trigger }), [state, trigger])
47+
48+
return result
49+
}

0 commit comments

Comments
 (0)