Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useFetch): new function #330

Merged
merged 25 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
47dec5c
feat(useWebSocket)!: rework useWebSocket
antfu Feb 9, 2021
0ffbe6c
feat(useFetch): initial implementation of useFetch
wheatjs Feb 12, 2021
38a9a94
updated signature overrides and documentation for UseFetchOptions
wheatjs Feb 12, 2021
f41c653
Renamed autoFetch to immediate
wheatjs Feb 12, 2021
954a037
Renamed autoRefetch to refetch
wheatjs Feb 12, 2021
9680574
Removed string from error message
wheatjs Feb 12, 2021
83b82ef
Updated demos and docs to reflect changes in API. Args now use length…
wheatjs Feb 12, 2021
8f9d850
Rename status to statusCode
wheatjs Feb 12, 2021
fc387cb
set statusCode to null on fetch
wheatjs Feb 12, 2021
b56d392
Reset varaibles on refetch and rename status to statusCode
wheatjs Feb 12, 2021
d692c28
Remove instanceof from Abort controller check
wheatjs Feb 12, 2021
0d96f10
Simplify refetch watch
wheatjs Feb 12, 2021
b9a8e2d
Patched watch bug for now. Started looking into apis for usePostJson
wheatjs Feb 14, 2021
237724c
chore: api design
antfu Feb 19, 2021
374ed94
fixed issue with new api implementation. Started working on tests
wheatjs Feb 19, 2021
cc8edad
Merge branch 'main' into main
antfu Feb 20, 2021
34e8fed
fixed issue with initialized check
wheatjs Feb 20, 2021
345d5de
Merge branch 'main' of github.com:jacobclevenger/vueuse into main
wheatjs Feb 20, 2021
cf21b90
Merge branch 'main' into main
antfu Feb 20, 2021
8530f98
chore: update docs
antfu Feb 20, 2021
ae739be
execute now returns a promise
wheatjs Feb 21, 2021
2c5092a
fixed overly verbose return in execute function
wheatjs Feb 21, 2021
acb7bf0
Added 'aborted' proprty to useFetch. Added in basic tests
wheatjs Feb 22, 2021
9e07337
Merge remote-tracking branch 'origin/main' into pr/jacobclevenger/330
antfu Feb 23, 2021
5ad7f98
chore: update locks
antfu Feb 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Expand Up @@ -18,5 +18,6 @@
"vue"
],
"as-fs.enabled": true,
"as-fs.path": "packages/.vitepress/.editor-as-fs"
"as-fs.path": "packages/.vitepress/.editor-as-fs",
"volar.tsPlugin": true
}
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -7,7 +7,7 @@ Collection of essential Vue Composition Utilities
<a href="https://www.npmjs.com/package/@vueuse/core" target="__blank"><img src="https://img.shields.io/npm/v/@vueuse/core?color=a1b858&label=" alt="NPM version"></a>
<a href="https://www.npmjs.com/package/@vueuse/core" target="__blank"><img alt="NPM Downloads" src="https://img.shields.io/npm/dm/@vueuse/core?color=50a36f&label="></a>
<a href="https://vueuse.org" target="__blank"><img src="https://img.shields.io/static/v1?label=&message=docs%20%26%20demos&color=1e8a7a" alt="Docs & Demos"></a>
<img alt="Function Count" src="https://img.shields.io/badge/-103%20functions-13708a">
<img alt="Function Count" src="https://img.shields.io/badge/-104%20functions-13708a">
<br>
<a href="https://github.com/vueuse/vueuse" target="__blank"><img alt="GitHub stars" src="https://img.shields.io/github/stars/vueuse/vueuse?style=social"></a>
</p>
Expand Down
7 changes: 7 additions & 0 deletions indexes.json
Expand Up @@ -458,6 +458,13 @@
"category": "Browser",
"description": "reactive favicon"
},
{
"name": "useFetch",
"package": "core",
"docs": "https://vueuse.org/core/useFetch/",
"category": "Browser",
"description": "reactive [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) with support for [aborting requests](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort)"
},
{
"name": "useFullscreen",
"package": "core",
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -80,6 +80,7 @@
"husky": "4.3.7",
"jest": "^26.6.3",
"jest-each": "^26.6.2",
"jest-fetch-mock": "^3.0.3",
"js-yaml": "^4.0.0",
"lint-staged": "^10.5.4",
"markdown-table": "^2.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/core/index.ts
Expand Up @@ -21,6 +21,7 @@ export * from './useElementVisibility'
export * from './useEventListener'
export * from './useEventSource'
export * from './useFavicon'
export * from './useFetch'
export * from './useFullscreen'
export * from './useGeolocation'
export * from './useIdle'
Expand Down
75 changes: 75 additions & 0 deletions packages/core/useFetch/demo.vue
@@ -0,0 +1,75 @@
<script setup lang="ts">
import { reactive, ref } from 'vue-demi'
import { stringify } from '@vueuse/docs-utils'
import { useToggle } from '@vueuse/shared'
import { useFetch } from '.'

const url = ref('https://httpbin.org/get')
const refetch = ref(false)

const toggleRefetch = useToggle(refetch)

// const { isFinished, canAbort, isFetching, statusCode, error, data, execute, abort } = useFetch(url, { immediate: false, refetch })

const {
data,
error,
abort,
statusCode,
isFetching,
isFinished,
canAbort,
execute,
} = useFetch(url, { immediate: false, refetch }).get()

const text = stringify(reactive({
isFinished,
isFetching,
canAbort,
statusCode,
error,
data,
}))

</script>

<template>
<div>
<div>
<note>The following URLs can be used to test different features of useFetch</note>
<div class="mt-2">
Normal Request:
<code>
https://httpbin.org/get
</code>
</div>
<div>
Abort Request:
<code>
https://httpbin.org/delay/10
</code>
</div>
<div>
Response Error:
<code>
http://httpbin.org/status/500
</code>
</div>
</div>

<input v-model="url" type="text">
<button @click="execute">
Execute
</button>
<button @click="toggleRefetch">
<carbon-checkmark v-if="refetch" />
<carbon-error v-else />

<span class="ml-2">{{ refetch ? 'Refetch On': 'Refetch Off' }}</span>
</button>
<button v-if="canAbort" class="orange" @click="abort">
Abort
</button>
<pre lang="yaml">{{ text }}</pre>
</div>
</template>
169 changes: 169 additions & 0 deletions packages/core/useFetch/index.md
@@ -0,0 +1,169 @@
---
category: Browser
---

# useFetch

Reactive [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) with support for [aborting requests](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort), in browsers that support it.

## Usage

```ts
import { useFetch } from '@vueuse/core'

const { isFinished, statusCode, error, data } = useFetch(url)
```

Prevent auto-calling the fetch request and do it manually instead

```ts
import { useFetch } from '@vueuse/core'

const { execute, data } = useFetch(url, { immediate: false })

execute()
```

Fetch as Blob

```ts
import { useFetch } from '@vueuse/core'

const { execute, data } = useFetch(url).blob()
```

Post a JSON

```ts
import { useFetch } from '@vueuse/core'

const { execute, data } = useFetch(url)
.post({ message: 'Hello' })
.json()
```

Abort a fetch

```ts
import { useFetch } from '@vueuse/core'

const { execute, data, isFetching, abort } = useFetch(url)

setTimeout(() => {
// timeout!
abort()
}, 1000)
```

Automatically refetch when your URL is a ref

```ts
import { useFetch } from '@vueuse/core'

const url = ref('https://httpbin.org/get')

const { data } = useFetch(url, { refetch: true })

setTimeout(() => {
// Request will be fetched again
url.value = 'https://httpbin.org/status/500'
}, 5000)
```


<!--FOOTER_STARTS-->
## Type Declarations

```typescript
interface UseFetchReturnBase<T> {
/**
* Indicates if the fetch request has finished
*/
isFinished: Ref<boolean>
/**
* The statusCode of the HTTP fetch response
*/
statusCode: Ref<number | null>
/**
* The raw response of the fetch response
*/
response: Ref<Response | null>
/**
* Any fetch errors that may have occured
*/
error: Ref<any>
/**
* The fetch response body, may either be JSON or text
*/
data: Ref<T | null>
/**
* Indicates if the request is currently being fetched.
*/
isFetching: Ref<boolean>
/**
* Indicates if the fetch request is able to be aborted
*/
canAbort: ComputedRef<boolean>
/**
* Indicates if the fetch request was aborted
*/
aborted: Ref<boolean>
/**
* Abort the fetch request
*/
abort: Fn
/**
* Manually call the fetch
*/
execute: () => Promise<any>
}
declare type PayloadType = "text" | "json" | "formData"
interface UseFetchReturnMethodConfigured<T> extends UseFetchReturnBase<T> {
json<JSON = any>(): UseFetchReturnBase<JSON>
text(): UseFetchReturnBase<string>
blob(): UseFetchReturnBase<Blob>
arrayBuffer(): UseFetchReturnBase<ArrayBuffer>
formData(): UseFetchReturnBase<FormData>
}
export interface UseFetchReturn<T> extends UseFetchReturnMethodConfigured<T> {
get(): UseFetchReturnMethodConfigured<T>
post(payload?: unknown, type?: PayloadType): UseFetchReturnMethodConfigured<T>
put(payload?: unknown, type?: PayloadType): UseFetchReturnMethodConfigured<T>
delete(
payload?: unknown,
type?: PayloadType
): UseFetchReturnMethodConfigured<T>
}
export interface UseFetchOptions {
/**
* Will automatically run fetch when `useFetch` is used
*
* @default true
*/
immediate?: boolean
/**
* Will automatically refetch when the URL is changed if the url is a ref
*
* @default false
*/
refetch?: MaybeRef<boolean>
}
export declare function useFetch<T>(url: MaybeRef<string>): UseFetchReturn<T>
export declare function useFetch<T>(
url: MaybeRef<string>,
useFetchOptions: UseFetchOptions
): UseFetchReturn<T>
export declare function useFetch<T>(
url: MaybeRef<string>,
options: RequestInit,
useFetchOptions?: UseFetchOptions
): UseFetchReturn<T>
export {}
```

## Source

[Source](https://github.com/vueuse/vueuse/blob/main/packages/core/useFetch/index.ts) • [Demo](https://github.com/vueuse/vueuse/blob/main/packages/core/useFetch/demo.vue) • [Docs](https://github.com/vueuse/vueuse/blob/main/packages/core/useFetch/index.md)


<!--FOOTER_ENDS-->
78 changes: 78 additions & 0 deletions packages/core/useFetch/index.test.ts
@@ -0,0 +1,78 @@
import { useFetch } from '.'
import fetchMock from 'jest-fetch-mock'
import { when } from '@vueuse/shared'
import { nextTick, ref } from 'vue-demi'

describe('useFetch', () => {
beforeEach(() => {
fetchMock.resetMocks()
fetchMock.enableMocks()
fetchMock.doMock()
})

test('should have status code of 200 and message of Hello World', async() => {
fetchMock.mockResponse('Hello World', { status: 200 })

const { data, statusCode, isFinished } = useFetch('https://example.com')

await when(isFinished).toBe(true)

expect(statusCode.value).toBe(200)
expect(data.value).toBe('Hello World')
})

test('should parse response as json', async() => {
fetchMock.mockResponse(JSON.stringify({ message: 'Hello World' }), { status: 200 })

const { data, isFinished } = useFetch('https://example.com').json()

await when(isFinished).toBe(true)

expect(data.value).toStrictEqual({ message: 'Hello World' })
})

test('should have an error on 400', async() => {
fetchMock.mockResponse('Hello World', { status: 400 })

const { error, statusCode, isFinished } = useFetch('https://example.com')

await when(isFinished).toBe(true)

expect(statusCode.value).toBe(400)
expect(error.value).toBe('Bad Request')
})

test('should abort request and set aborted to true', async() => {
fetchMock.mockResponse(() => new Promise(resolve => setTimeout(() => resolve({ body: 'ok' }), 1000)))

const { aborted, abort, isFinished } = useFetch('https://example.com')

setTimeout(() => abort(), 0)

await when(isFinished).toBe(true)
expect(aborted.value).toBe(true)
})

test('should not call if immediate is false', async() => {
fetchMock.mockResponse('Hello World')

useFetch('https://example.com', { immediate: false })
await nextTick()

expect(fetchMock).toBeCalledTimes(0)
})

test('should refetch if refetch is set to true', async() => {
fetchMock.mockResponse('Hello World')

const url = ref('https://example.com')
const { isFinished } = useFetch(url, { refetch: true })

await when(isFinished).toBe(true)
url.value = 'https://example.com/test'
await nextTick()
await when(isFinished).toBe(true)

expect(fetchMock).toBeCalledTimes(2)
})
})