Skip to content

useFetch

Category
Export Size
2.23 kB
Last Changed
5 days ago

Reactive 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.

Demo

Usage

Basic Usage

The useFetch function can be used by simply providing a url. The url can be either a string or a controllable state. The data value will contain the result of the request, the error value will contain any errors, and the isFetching value will indicate if the request is loading.

ts
import { 
useFetch
} from '@reause/core'
const {
isFetching
,
error
,
data
} =
useFetch
(url)

Asynchronous Usage

useFetch can also be awaited just like a normal fetch:

ts
const { 
isFetching
,
error
,
data
} = await
useFetch
(url)

Refetching on URL change

Using a plain value for the url parameter (e.g. driven by useState) will allow the useFetch function to automatically trigger another request when the url changes.

tsx
import { 
useFetch
} from '@reause/core'
import {
useState
} from 'react'
const [
url
,
setUrl
] =
useState
('https://my-api.com/user/1')
const {
data
} =
useFetch
(
url
, {
refetch
: true })
setUrl
('https://my-api.com/user/2') // Will trigger another request

Prevent request from firing immediately

Setting the immediate option to false will prevent the request from firing until the execute function is called.

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

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
const { 
abort
,
canAbort
} =
useFetch
(url)
setTimeout
(() => {
if (
canAbort
)
abort
()
}, 100)

A request can also be aborted automatically by using timeout property. It will call abort function when the given timeout is reached.

ts
const { 
data
} =
useFetch
(url, {
timeout
: 100 })

Intercepting a request

The beforeFetch option can intercept a request before it is sent and modify the request options and url.

ts
const { 
data
} =
useFetch
(url, {
async
beforeFetch
({
url
,
options
,
cancel
}) {
const
myToken
= await getMyToken()
if (!
myToken
)
cancel
()
options
.
headers
= {
...
options
.
headers
,
Authorization
: `Bearer ${
myToken
}`,
} return {
options
,
} }, })

The afterFetch option can intercept the response data before it is updated.

ts
const { 
data
} =
useFetch
(url, {
afterFetch
(
ctx
) {
if (
ctx
.
data
.title === 'HxH')
ctx
.
data
.title = 'Hunter x Hunter' // Modifies the response data
return
ctx
}, })

The onFetchError option can intercept the response data and error before it is updated when updateDataOnError is set to true.

ts
const { 
data
} =
useFetch
(url, {
updateDataOnError
: true,
onFetchError
(
ctx
) {
// ctx.data can be null when 5xx response if (
ctx
.
data
=== null)
ctx
.
data
= {
title
: 'Hunter x Hunter' } // Modifies the response data
ctx
.
error
= new
Error
('Custom Error') // Modifies the error
return
ctx
}, })
console
.
log
(
data
) // { title: 'Hunter x Hunter' }

Setting the request method and return type

The request method and return type can be set by adding the appropriate methods to the end of useFetch

ts
// Request will be sent with GET method and data will be parsed as JSON
const { 
data
} =
useFetch
(url).
get
().
json
()
// Request will be sent with POST method and data will be parsed as text const {
data
} =
useFetch
(url).
post
().
text
()
// Or set the method using the options // Request will be sent with GET method and data will be parsed as blob const {
data
} =
useFetch
(url, {
method
: 'GET' }, {
refetch
: true }).
blob
()

Creating a Custom Instance

The createFetch function will return a useFetch function with whatever pre-configured options that are provided to it. This is useful for interacting with API's throughout an application that uses the same base URL or needs Authorization headers.

ts
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',
}, }) const {
isFetching
,
error
,
data
} =
useMyFetch
('users')

If you want to control the behavior of beforeFetch, afterFetch, onFetchError between the pre-configured instance and newly spawned instance. You can provide a combination option to toggle between overwrite or chaining.

ts
const 
useMyFetch
=
createFetch
({
baseUrl
: 'https://my-api.com',
combination
: 'overwrite',
options
: {
// beforeFetch in pre-configured instance will only run when the newly spawned instance do not pass beforeFetch async
beforeFetch
({
options
}) {
const
myToken
= await getMyToken()
options
.
headers
.Authorization = `Bearer ${
myToken
}`
return {
options
}
}, }, }) // use useMyFetch beforeFetch const {
isFetching
,
error
,
data
} =
useMyFetch
('users')
// use custom beforeFetch const {
isFetching
,
error
,
data
} =
useMyFetch
('users', {
async
beforeFetch
({
url
,
options
,
cancel
}) {
const
myToken
= await getMyToken()
if (!
myToken
)
cancel
()
options
.
headers
= {
...
options
.
headers
,
Authorization
: `Bearer ${
myToken
}`,
} return {
options
,
} }, })

You can re-execute the request by calling the execute method in afterFetch or onFetchError. Here is a simple example of refreshing a token:

ts
let 
isRefreshing
= false
const
refreshSubscribers
:
Array
<() => void> = []
const
useMyFetch
=
createFetch
({
baseUrl
: 'https://my-api.com',
options
: {
async
beforeFetch
({
options
}) {
const
myToken
= await getMyToken()
options
.
headers
.Authorization = `Bearer ${
myToken
}`
return {
options
}
},
afterFetch
({
data
,
response
,
context
,
execute
}) {
if (needRefreshToken) { if (!
isRefreshing
) {
isRefreshing
= true
refreshToken
().
then
((
newToken
) => {
if (
newToken
) {
isRefreshing
= false
setMyToken(
newToken
)
onRefreshed
()
} else {
refreshSubscribers
.
length
= 0
// handle refresh token error } }) } return new
Promise
((
resolve
) => {
addRefreshSubscriber
(() => {
execute
().
then
((
response
) => {
resolve
({
data
,
response
})
}) }) }) } return {
data
,
response
}
}, // or use onFetchError with updateDataOnError
updateDataOnError
: true,
onFetchError
({
error
,
data
,
response
,
context
,
execute
}) {
// same as afterFetch return {
error
,
data
}
}, },
fetchOptions
: {
mode
: 'cors',
}, }) async function
refreshToken
() {
const {
data
,
execute
} =
useFetch
<string>('refresh-token', {
immediate
: false,
}) await
execute
()
return
data
} function
onRefreshed
() {
refreshSubscribers
.
forEach
(
callback
=>
callback
())
refreshSubscribers
.
length
= 0
} function
addRefreshSubscriber
(
callback
: () => void) {
refreshSubscribers
.
push
(
callback
)
} const {
isFetching
,
error
,
data
} =
useMyFetch
('users')

Events

The onFetchResponse and onFetchError will fire on fetch request responses and errors respectively.

ts
const { 
onFetchResponse
,
onFetchError
} =
useFetch
(url)
onFetchResponse
((
response
) => {
console
.
log
(
response
.
status
)
})
onFetchError
((
error
) => {
console
.
error
(
error
.message)
})

Type Declarations

Toggle
ts
export interface UseFetchReturn<T> {
    isFinished: boolean;
    statusCode: number | null;
    setStatusCode: Dispatch<SetStateAction<number | null>>;
    response: Response | null;
    setResponse: Dispatch<SetStateAction<Response | null>>;
    error: any;
    setError: Dispatch<SetStateAction<any>>;
    data: T | null;
    setData: Dispatch<SetStateAction<T | null>>;
    isFetching: boolean;
    canAbort: boolean;
    aborted: boolean;
    setAborted: Dispatch<SetStateAction<boolean>>;
    abort: (reason?: any) => void;
    execute: (throwOnFailed?: boolean) => Promise<any>;
    onFetchResponse: EventHookOn<Response>;
    onFetchError: EventHookOn;
    onFetchFinally: EventHookOn;
    get: () => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>;
    post: (payload?: unknown, type?: string) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>;
    put: (payload?: unknown, type?: string) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>;
    delete: (payload?: unknown, type?: string) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>;
    patch: (payload?: unknown, type?: string) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>;
    head: (payload?: unknown, type?: string) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>;
    options: (payload?: unknown, type?: string) => UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>;
    json: <JSON = any>() => UseFetchReturn<JSON> & PromiseLike<UseFetchReturn<JSON>>;
    text: () => UseFetchReturn<string> & PromiseLike<UseFetchReturn<string>>;
    blob: () => UseFetchReturn<Blob> & PromiseLike<UseFetchReturn<Blob>>;
    arrayBuffer: () => UseFetchReturn<ArrayBuffer> & PromiseLike<UseFetchReturn<ArrayBuffer>>;
    formData: () => UseFetchReturn<FormData> & PromiseLike<UseFetchReturn<FormData>>;
}

export interface BeforeFetchContext {
    url: string;
    options: RequestInit;
    cancel: () => void;
}

export interface AfterFetchContext<T = any> {
    response: Response;
    data: T | null;
    context: BeforeFetchContext;
    execute: (throwOnFailed?: boolean) => Promise<any>;
}

export interface OnFetchErrorContext<T = any, E = any> {
    error: E;
    data: T | null;
    response: Response | null;
    context: BeforeFetchContext;
    execute: (throwOnFailed?: boolean) => Promise<any>;
}

export interface UseFetchOptions {
    fetch?: typeof window.fetch;
    immediate?: boolean;
    refetch?: boolean;
    initialData?: any;
    timeout?: number;
    updateDataOnError?: boolean;
    beforeFetch?: (ctx: BeforeFetchContext) => Promise<Partial<BeforeFetchContext> | void> | Partial<BeforeFetchContext> | void;
    afterFetch?: (ctx: AfterFetchContext) => Promise<Partial<AfterFetchContext>> | Partial<AfterFetchContext>;
    onFetchError?: (ctx: OnFetchErrorContext) => Promise<Partial<OnFetchErrorContext>> | Partial<OnFetchErrorContext>;
}

export interface CreateFetchOptions {
    baseUrl?: string;
    combination?: Combination;
    options?: UseFetchOptions;
    fetchOptions?: RequestInit;
}

type EventHookOn<T = any> = (fn: (param: T) => void) => () => void

type Combination = 'overwrite' | 'chain'

export function createFetch(config?: CreateFetchOptions)

export function useFetch<T>(url: string): UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>

export function useFetch<T>(url: string, useFetchOptions: UseFetchOptions): UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>

export function useFetch<T>(url: string, options: RequestInit, useFetchOptions?: UseFetchOptions): UseFetchReturn<T> & PromiseLike<UseFetchReturn<T>>

Source

Source · Demo · VueUse

Contributors

hairyf

Changelog

v0.1.0 on
e608a - feat(ci): align CI, packaging and release with VueUse
53394 - fix(core): resolve useFetch audit findings (#557)
8fddc - chore!: remove all Vue-only Maybe* types and getter unions, adopt React Ref (#462)

Released under the MIT License. v0.1.8