diff --git a/packages/core/useAsyncState/index.md b/packages/core/useAsyncState/index.md index 8fe4c333b48e..20f55fbb198a 100644 --- a/packages/core/useAsyncState/index.md +++ b/packages/core/useAsyncState/index.md @@ -72,7 +72,7 @@ export declare function useAsyncState( } | undefined > - execute: (delay?: number) => void + execute: (delay?: number) => Promise } ``` diff --git a/packages/core/useFetch/demo.vue b/packages/core/useFetch/demo.vue index 3290042acb67..e161cf0040eb 100644 --- a/packages/core/useFetch/demo.vue +++ b/packages/core/useFetch/demo.vue @@ -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, @@ -20,7 +18,7 @@ const { isFinished, canAbort, execute, -} = useFetch(url, { immediate: false, refetch }).get() +} = useFetch(url, { refetch }).get() const text = stringify(reactive({ isFinished, diff --git a/packages/core/useFetch/index.md b/packages/core/useFetch/index.md index 7a9fa50e26e7..5f145aae254e 100644 --- a/packages/core/useFetch/index.md +++ b/packages/core/useFetch/index.md @@ -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') +``` ## Type Declarations @@ -148,6 +169,23 @@ export interface UseFetchOptions { */ refetch?: MaybeRef } +export interface CreateFetchOptions { + /** + * The base URL that will be prefixed to all urls + */ + baseUrl?: MaybeRef + /** + * 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(url: MaybeRef): UseFetchReturn export declare function useFetch( url: MaybeRef, diff --git a/packages/core/useFetch/index.test.ts b/packages/core/useFetch/index.test.ts index 1884973251d3..074d9027940d 100644 --- a/packages/core/useFetch/index.test.ts +++ b/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' @@ -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') @@ -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() @@ -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 }) @@ -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') + }) }) diff --git a/packages/core/useFetch/index.ts b/packages/core/useFetch/index.ts index b546416676a5..588462e61cd4 100644 --- a/packages/core/useFetch/index.ts +++ b/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 { /** @@ -89,6 +89,69 @@ export interface UseFetchOptions { refetch?: MaybeRef } +export interface CreateFetchOptions { + /** + * The base URL that will be prefixed to all urls + */ + baseUrl?: MaybeRef + + /** + * 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, ...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(url: MaybeRef): UseFetchReturn export function useFetch(url: MaybeRef, useFetchOptions: UseFetchOptions): UseFetchReturn export function useFetch(url: MaybeRef, options: RequestInit, useFetchOptions?: UseFetchOptions): UseFetchReturn @@ -107,14 +170,14 @@ export function useFetch(url: MaybeRef, ...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] } } @@ -267,3 +330,10 @@ export function useFetch(url: MaybeRef, ...args: any[]): UseFetchRetu return shell } + +function joinPaths(start: string, end: string): string { + if (!start.endsWith('/') && !end.startsWith('/')) + return `${start}/${end}` + + return `${start}${end}` +} diff --git a/packages/shared/utils/index.ts b/packages/shared/utils/index.ts index d0e2976a5fc0..71660d4ca885 100644 --- a/packages/shared/utils/index.ts +++ b/packages/shared/utils/index.ts @@ -18,3 +18,7 @@ export function promiseTimeout( export function invoke(fn: () => T): T { return fn() } + +export function containsProp(obj: object, ...props: string[]) { + return props.some(k => k in obj) +}