From ae7821df67311cf41cb0cd65699504c310ab62e9 Mon Sep 17 00:00:00 2001 From: wheat Date: Sat, 13 Mar 2021 00:33:06 -0500 Subject: [PATCH] chore(useFetch): add tests and update docs for useFetch (#375) --- packages/core/useFetch/index.md | 119 ++++++++++++++++----------- packages/core/useFetch/index.test.ts | 34 ++++++++ packages/core/useFetch/index.ts | 2 +- 3 files changed, 104 insertions(+), 51 deletions(-) diff --git a/packages/core/useFetch/index.md b/packages/core/useFetch/index.md index 64d91a77c50d..8135447a2e6c 100644 --- a/packages/core/useFetch/index.md +++ b/packages/core/useFetch/index.md @@ -2,94 +2,113 @@ 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. +Reactive [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) provides the ability to abort requests, intercept requests before +they are fired, automatically refetch requests when the url changes, and create your own `useFetch` with predefined options. + +[[toc]] ## Usage +### Basic Usage +The `useFetch` funciton can be used by simply providing a url. The url can be either a string or a `ref`. The `data` +object will contain the result of the request, the `error` object will contain any errors, and the `isFetching` object will +indicate if the request is loading. ```ts import { useFetch } from '@vueuse/core' -const { isFinished, statusCode, error, data } = useFetch(url) +const { isFetching, error, data } = useFetch(url) ``` -Prevent auto-calling the fetch request and do it manually instead - +### Refetching on URL change +Using a `ref` for the url parameter will allow the `useFetch` function to automatically trigger another +request when the url is changed. ```ts -import { useFetch } from '@vueuse/core' +const url = ref('https://my-api.com/user/1') -const { execute, data } = useFetch(url, { immediate: false }) +const { data } = useFetch(url, { refetch: true }) -execute() +url.value = 'https://my-api.com/user/2' // Will trigger another request ``` -Fetch as Blob - +### Prevent request from firing immediately +Setting the `immediate` option to false will prevent the request from firing until the `execute` +function is called. ```ts -import { useFetch } from '@vueuse/core' +const { execute } = useFetch(url, { immediate: false }) -const { execute, data } = useFetch(url).blob() +execute() ``` -Post a JSON - +### Aborting a request +A request can be aborted by using the `abort` function from the `useFetch` function. The `canAbort` property indicates +if the request can be aborted ```ts -import { useFetch } from '@vueuse/core' +const { abort, canAbort } = useFetch(url) -const { execute, data } = useFetch(url) - .post({ message: 'Hello' }) - .json() +setTimeout(() => { + if (canAbort.value) + abort() +}, 100) ``` -Abort a fetch - +### Intercepting a request +The `beforeFetch` option can intercept a request before it is sent and modify the request options and url. ```ts -import { useFetch } from '@vueuse/core' +const { data } = useFetch(url, { + async beforeFetch({ url, options, cancel }) { + const myToken = await getMyToken() -const { execute, data, isFetching, abort } = useFetch(url) + if (!myToken) + cancel() -setTimeout(() => { - // timeout! - abort() -}, 1000) + options.headers.Authorization = `Bearer ${myToken}` + + return { + options + } + } +}) ``` -Automatically refetch when your URL is a ref +### Setting the request method and return type +The requset method and return type can be set by adding the appropite methods to the end of `useFetch` ```ts -import { useFetch } from '@vueuse/core' +// Request will be sent with GET method and data will be parsed as JSON +const { data } = useFetch(url).get().json() -const url = ref('https://httpbin.org/get') +// Request will be sent with POST method and data will be parsed as text +const { data } = useFetch(url).post().text() -const { data } = useFetch(url, { refetch: true }) +// Or set the method using the options -setTimeout(() => { - // Request will be fetched again - url.value = 'https://httpbin.org/status/500' -}, 5000) +// Request will be sent with GET method and data will be parsed as blob +const { data } = useFetch(url, { method: 'GET' }, { refetch: true }).blob() ``` -Create a custom `useFetch` instance with default values - +### Creating a custom instance +The `createFetch` function will return a useFetch function with whatever preconfigured options that are provided to it. +This is useful for interacting with API's thoughout an application that uses the same base URL or needs Authorization headers. ```ts -// foo.ts -import { createFetch } from '@vueuse/core' - -export const useMyFetch = createFetch({ - baseUrl: 'https://my-api.com', - headers: { - Authorization: 'my-token', - } +const useMyFetch = createFetch({ + baseUrl: 'https://my-api.com', + options: { + async beforeFetch({ options }) { + const myToken = await getMyToken() + options.headers.Authorization = `Bearer ${myToken}` + + return { options } + }, + }, + fetchOptions: { + mode: 'cors', + }, }) -``` - -```ts -// bar.ts -import { useMyFetch } from './foo' -// will request `https://my-api.com/posts` with token -const { data } = useFetch('/posts') +const { isFetching, error, data } = useMyFetch('users') ``` diff --git a/packages/core/useFetch/index.test.ts b/packages/core/useFetch/index.test.ts index 92715640751b..1dede319fafc 100644 --- a/packages/core/useFetch/index.test.ts +++ b/packages/core/useFetch/index.test.ts @@ -94,4 +94,38 @@ describe('useFetch', () => { 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') }) + + test('should run the beforeFetch function and add headers to the request', async() => { + fetchMock.mockResponse('') + + const { isFinished } = useFetch('https://example.com', { headers: { 'Accept-Language': 'en-US' } }, { + beforeFetch({ options }) { + options.headers = { + ...options.headers, + Authorization: 'my-auth-token', + } + + return { options } + }, + }) + + await when(isFinished).toBe(true) + + expect(fetchMock.mock.calls[0][1]!.headers).toMatchObject({ Authorization: 'my-auth-token', 'Accept-Language': 'en-US' }) + }) + + test('should run the beforeFetch function and cancel the request', async() => { + fetchMock.mockResponse('') + + const { execute } = useFetch('https://example.com', { + immediate: false, + beforeFetch({ cancel }) { + cancel() + }, + }) + + await execute() + + expect(fetchMock).toBeCalledTimes(0) + }) }) diff --git a/packages/core/useFetch/index.ts b/packages/core/useFetch/index.ts index 1f2e9fe02e74..ba7643ac14ef 100644 --- a/packages/core/useFetch/index.ts +++ b/packages/core/useFetch/index.ts @@ -114,7 +114,7 @@ export interface UseFetchOptions { /** * Will run immediately before the fetch request is dispatched */ - beforeFetch?: (ctx: BeforeFetchContext) => Promise> | Partial + beforeFetch?: (ctx: BeforeFetchContext) => Promise | void> | Partial | void } export interface CreateFetchOptions {