Skip to content

Commit

Permalink
feat(useFetch): new feature createFetch, close vitest-dev#352 (vitest…
Browse files Browse the repository at this point in the history
…-dev#353)

* feat(useFetch): new feature createFetch

* Update packages/core/useFetch/index.ts

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>

* removed unnecessary overloads

* Update packages/core/useFetch/index.ts

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>

* baseUrl is now optional

* added function to check type of args

* removed console log from useFetch test

* Update packages/core/useFetch/index.ts

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>

* merged imports in useFetch

* chore: update

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
wheatjs and antfu committed Feb 28, 2021
1 parent f28591a commit cc8656a
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 11 deletions.
2 changes: 1 addition & 1 deletion packages/core/useAsyncState/index.md
Expand Up @@ -72,7 +72,7 @@ export declare function useAsyncState<T>(
}
| undefined
>
execute: (delay?: number) => void
execute: (delay?: number) => Promise<void>
}
```

Expand Down
4 changes: 1 addition & 3 deletions packages/core/useFetch/demo.vue
Expand Up @@ -9,8 +9,6 @@ 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,
Expand All @@ -20,7 +18,7 @@ const {
isFinished,
canAbort,
execute,
} = useFetch(url, { immediate: false, refetch }).get()
} = useFetch(url, { refetch }).get()
const text = stringify(reactive({
isFinished,
Expand Down
38 changes: 38 additions & 0 deletions packages/core/useFetch/index.md
Expand Up @@ -70,6 +70,27 @@ setTimeout(() => {
}, 5000)
```

Create a custom `useFetch` instance with default values

```ts
// foo.ts
import { createFetch } from '@vueuse/core'

export const useMyFetch = createFetch({
baseUrl: 'https://my-api.com',
headers: {
Authorization: 'my-token',
}
})
```

```ts
// bar.ts
import { useMyFetch } from './foo'

// will request `https://my-api.com/posts` with token
const { data } = useFetch('/posts')
```

<!--FOOTER_STARTS-->
## Type Declarations
Expand Down Expand Up @@ -148,6 +169,23 @@ export interface UseFetchOptions {
*/
refetch?: MaybeRef<boolean>
}
export interface CreateFetchOptions {
/**
* The base URL that will be prefixed to all urls
*/
baseUrl?: MaybeRef<string>
/**
* Default Options for the useFetch function
*/
options?: UseFetchOptions
/**
* Options for the fetch request
*/
fetchOptions?: RequestInit
}
export declare function createFetch(
config?: CreateFetchOptions
): typeof useFetch
export declare function useFetch<T>(url: MaybeRef<string>): UseFetchReturn<T>
export declare function useFetch<T>(
url: MaybeRef<string>,
Expand Down
20 changes: 16 additions & 4 deletions packages/core/useFetch/index.test.ts
@@ -1,4 +1,4 @@
import { useFetch } from '.'
import { useFetch, createFetch } from '.'
import fetchMock from 'jest-fetch-mock'
import { when } from '@vueuse/shared'
import { nextTick, ref } from 'vue-demi'
Expand Down Expand Up @@ -32,7 +32,7 @@ describe('useFetch', () => {
})

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

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

Expand All @@ -54,7 +54,7 @@ describe('useFetch', () => {
})

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

useFetch('https://example.com', { immediate: false })
await nextTick()
Expand All @@ -63,7 +63,7 @@ describe('useFetch', () => {
})

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

const url = ref('https://example.com')
const { isFinished } = useFetch(url, { refetch: true })
Expand All @@ -75,4 +75,16 @@ describe('useFetch', () => {

expect(fetchMock).toBeCalledTimes(2)
})

test('should create an instance of useFetch with a base url', async() => {
fetchMock.mockResponse('')

const useMyFetch = createFetch({ baseUrl: 'https://example.com', fetchOptions: { headers: { Authorization: 'test' } } })
const { isFinished } = useMyFetch('test', { headers: { 'Accept-Language': 'en-US' } })

await when(isFinished).toBe(true)

expect(fetchMock.mock.calls[0][1]!.headers).toMatchObject({ Authorization: 'test', 'Accept-Language': 'en-US' })
expect(fetchMock.mock.calls[0][0]).toEqual('https://example.com/test')
})
})
76 changes: 73 additions & 3 deletions packages/core/useFetch/index.ts
@@ -1,5 +1,5 @@
import { Ref, ref, unref, watch, computed, ComputedRef, shallowRef } from 'vue-demi'
import { Fn, MaybeRef } from '@vueuse/shared'
import { Fn, MaybeRef, containsProp } from '@vueuse/shared/utils'

interface UseFetchReturnBase<T> {
/**
Expand Down Expand Up @@ -89,6 +89,69 @@ export interface UseFetchOptions {
refetch?: MaybeRef<boolean>
}

export interface CreateFetchOptions {
/**
* The base URL that will be prefixed to all urls
*/
baseUrl?: MaybeRef<string>

/**
* Default Options for the useFetch function
*/
options?: UseFetchOptions

/**
* Options for the fetch request
*/
fetchOptions?: RequestInit
}

/**
* !!!IMPORTANT!!!
*
* If you update the UseFetchOptions interface, be sure to update this object
* to include the new options
*/
function isFetchOptions(obj: object): obj is UseFetchOptions {
return containsProp(obj, 'immediate', 'refetch')
}

export function createFetch(config: CreateFetchOptions = {}) {
let options = config.options || {}
let fetchOptions = config.fetchOptions || {}

function useFactoryFetch(url: MaybeRef<string>, ...args: any[]) {
const computedUrl = computed(() => config.baseUrl
? joinPaths(unref(config.baseUrl), unref(url))
: unref(url),
)

// Merge properties into a single object
if (args.length > 0) {
if (isFetchOptions(args[0])) {
options = { ...options, ...args[0] }
}
else {
fetchOptions = {
...fetchOptions,
...args[0],
headers: {
...(fetchOptions.headers || {}),
...(args[0].headers || {}),
},
}
}
}

if (args.length > 1 && isFetchOptions(args[1]))
options = { ...options, ...args[1] }

return useFetch(computedUrl, fetchOptions, options)
}

return useFactoryFetch as typeof useFetch
}

export function useFetch<T>(url: MaybeRef<string>): UseFetchReturn<T>
export function useFetch<T>(url: MaybeRef<string>, useFetchOptions: UseFetchOptions): UseFetchReturn<T>
export function useFetch<T>(url: MaybeRef<string>, options: RequestInit, useFetchOptions?: UseFetchOptions): UseFetchReturn<T>
Expand All @@ -107,14 +170,14 @@ export function useFetch<T>(url: MaybeRef<string>, ...args: any[]): UseFetchRetu
let initialized = false

if (args.length > 0) {
if ('immediate' in args[0] || 'refetch' in args[0])
if (isFetchOptions(args[0]))
options = { ...options, ...args[0] }
else
fetchOptions = args[0]
}

if (args.length > 1) {
if ('immediate' in args[1] || 'refetch' in args[1])
if (isFetchOptions(args[1]))
options = { ...options, ...args[1] }
}

Expand Down Expand Up @@ -267,3 +330,10 @@ export function useFetch<T>(url: MaybeRef<string>, ...args: any[]): UseFetchRetu

return shell
}

function joinPaths(start: string, end: string): string {
if (!start.endsWith('/') && !end.startsWith('/'))
return `${start}/${end}`

return `${start}${end}`
}
4 changes: 4 additions & 0 deletions packages/shared/utils/index.ts
Expand Up @@ -18,3 +18,7 @@ export function promiseTimeout(
export function invoke<T>(fn: () => T): T {
return fn()
}

export function containsProp(obj: object, ...props: string[]) {
return props.some(k => k in obj)
}

0 comments on commit cc8656a

Please sign in to comment.