useFetch
Features
- SSR (server side rendering) support
- TypeScript support
- 1 dependency (use-ssr)
- GraphQL support (queries + mutations)
- Provider to set default
url
and options
- Request/response interceptors
- React Native support
Usage
Examples
Basic Usage (managed state) useFetch
import useFetch from 'use-http'
function Todos() {
const [todos, setTodos] = useState([])
const [request, response] = useFetch('https://example.com')
const mounted = useRef(false)
useEffect(() => {
if (mounted.current) return
mounted.current= true
initializeTodos()
})
async function initializeTodos() {
const initialTodos = await request.get('/todos')
if (response.ok) setTodos(initialTodos)
}
async function addTodo() {
const newTodo = await request.post('/todos', {
title: 'no way',
})
if (response.ok) setTodos([...todos, newTodo])
}
return (
<>
<button onClick={addTodo}>Add Todo</button>
{request.error && 'Error!'}
{request.loading && 'Loading...'}
{todos.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}
Basic Usage (no managed state) useFetch
import useFetch from 'use-http'
function Todos() {
const options = {
onMount: true,
data: []
}
const { loading, error, data } = useFetch('https://example.com/todos', options)
return (
<>
{error && 'Error!'}
{loading && 'Loading...'}
{data.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}
Basic Usage with Provider
import useFetch, { Provider } from 'use-http'
function Todos() {
const { loading, error, data } = useFetch({
onMount: true,
path: '/todos',
data: []
})
return (
<>
{error && 'Error!'}
{loading && 'Loading...'}
{data.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}
const App = () => (
<Provider url='https://example.com'>
<Todos />
</Provider>
)

Destructured useFetch
â ď¸ The response
object cannot be destructured! (at least not currently) ď¸ď¸â ď¸
var [request, response, loading, error] = useFetch('https://example.com')
var {
request,
response,
loading,
error,
data,
get,
post,
put,
patch,
delete
del,
mutate,
query,
abort
} = useFetch('https://example.com')
var {
loading,
error,
data,
get,
post,
put,
patch,
delete
del,
mutate,
query,
abort
} = request
Relative routes useFetch
â ď¸ baseUrl
is no longer supported, it is now only url
var request = useFetch({ url: 'https://example.com' })
var request = useFetch('https://example.com')
request.post('/todos', {
no: 'way'
})
Abort useFetch
const githubRepos = useFetch({
url: `https://api.github.com/search/repositories?q=`
})
const searchGithubRepos = e => githubRepos.get(encodeURI(e.target.value))
<>
<input onChange={searchGithubRepos} />
<button onClick={githubRepos.abort}>Abort</button>
{githubRepos.loading ? 'Loading...' : githubRepos.data.items.map(repo => (
<div key={repo.id}>{repo.name}</div>
))}
</>
GraphQL Query useFetch
const QUERY = `
query Todos($userID string!) {
todos(userID: $userID) {
id
title
}
}
`
function App() {
const request = useFetch('http://example.com')
const getTodosForUser = id => request.query(QUERY, { userID: id })
return (
<>
<button onClick={() => getTodosForUser('theUsersID')}>Get User's Todos</button>
{request.loading ? 'Loading...' : <pre>{request.data}</pre>}
</>
)
}
GraphQL Mutation useFetch
The Provider
allows us to set a default url
, options
(such as headers) and so on.
const MUTATION = `
mutation CreateTodo($todoTitle string) {
todo(title: $todoTitle) {
id
title
}
}
`
function App() {
const [todoTitle, setTodoTitle] = useState('')
const request = useFetch('http://example.com')
const createtodo = () => request.mutate(MUTATION, { todoTitle })
return (
<>
<input onChange={e => setTodoTitle(e.target.value)} />
<button onClick={createTodo}>Create Todo</button>
{request.loading ? 'Loading...' : <pre>{request.data}</pre>}
</>
)
}
Provider
using the GraphQL useMutation
and useQuery
Query for todos
import { useQuery } from 'use-http'
export default function QueryComponent() {
const request = useQuery`
query Todos($userID string!) {
todos(userID: $userID) {
id
title
}
}
`
const getTodosForUser = id => request.query({ userID: id })
return (
<>
<button onClick={() => getTodosForUser('theUsersID')}>Get User's Todos</button>
{request.loading ? 'Loading...' : <pre>{request.data}</pre>}
</>
)
}

Add a new todo
import { useMutation } from 'use-http'
export default function MutationComponent() {
const [todoTitle, setTodoTitle] = useState('')
const [data, loading, error, mutate] = useMutation`
mutation CreateTodo($todoTitle string) {
todo(title: $todoTitle) {
id
title
}
}
`
const createTodo = () => mutate({ todoTitle })
return (
<>
<input onChange={e => setTodoTitle(e.target.value)} />
<button onClick={createTodo}>Create Todo</button>
{loading ? 'Loading...' : <pre>{data}</pre>}
</>
)
}
Adding the Provider
These props are defaults used in every request inside the <Provider />
. They can be overwritten individually
import { Provider } from 'use-http'
import QueryComponent from './QueryComponent'
import MutationComponent from './MutationComponent'
function App() {
const options = {
headers: {
Authorization: 'Bearer YOUR_TOKEN_HERE'
}
}
return (
<Provider url='http://example.com' options={options}>
<QueryComponent />
<MutationComponent />
<Provider/>
)
}
Request/Response Interceptors
This example shows how we can do authentication in the request
interceptor and how we can camelCase the results in the response
interceptor
import { Provider } from 'use-http'
import { toCamel } from 'convert-keys'
function App() {
let [token] = useLocalStorage('token')
const options = {
interceptors: {
request: async (options) => {
if (isExpired(token)) token = await getNewToken()
options.headers.Authorization = `Bearer ${token}`
return options
},
response: (response) => {
if (response.data) response.data = toCamel(response.data)
return response
}
}
}
return (
<Provider url='http://example.com' options={options}>
<SomeComponent />
<Provider/>
)
}

File Uploads (FormData)
This example shows how we can upload a file using useFetch
.
import useFetch from 'use-http'
const FileUploader = () => {
const [file, setFile] = useState()
const { post } = useFetch('https://example.com/upload')
const uploadFile = async () => {
const data = new FormData()
data.append('file', file)
if (file instanceof FormData) await post(data)
}
return (
<div>
{/* Drop a file onto the input below */}
<input onChange={e => setFile(e.target.files[0])} />
<button onClick={uploadFile}>Upload</button>
</div>
)
}
Handling Different Response Types
This example shows how we can get .json()
, .text()
, .formData()
, .blob()
, .arrayBuffer()
, and all the other http response methods. By default, useFetch
1st tries to call response.json()
under the hood, if that fails it's backup is response.text()
. If that fails, then you need a different response type which is where this comes in.
import useFetch from 'use-http'
const App = () => {
const [name, setName] = useState('')
const { get, loading, error, data, response } = useFetch('http://example.com')
const handleClick = async () => {
await get('/users/1?name=true')
const text = await response.text()
setName(text)
}
return (
<>
<button onClick={handleClick}>Load Data</button>
{error && error.messge}
{loading && "Loading..."}
{data && <div>{name}</div>}
</>
)
}

Overview
Hooks
Hook | Description |
---|
useFetch | The base hook |
useQuery | For making a GraphQL query |
useMutation | For making a GraphQL mutation |
Options
This is exactly what you would pass to the normal js fetch
, with a little extra. All these options can be passed to the <Provider options={/* every option below */} />
, or directly to useFetch
. If you have both in the <Provider />
and in useFetch
, the useFetch
options will overwrite the ones from the <Provider />
Option | Description | Default |
---|
url | Allows you to set a base path so relative paths can be used for each request :) | empty string |
onMount | Once the component mounts, the http request will run immediately | false |
onUpdate | This is essentially the same as the dependency array for useEffect. Whenever one of the variables in this array is updated, the http request will re-run. | [] |
onAbort | Runs when the request is aborted. | empty function |
onTimeout | Called when the request times out. | empty function |
retries | When a request fails or times out, retry the request this many times. By default it will not retry. | 0 |
timeout | The request will be aborted/cancelled after this amount of time. This is also the interval at which retries will be made at. in milliseconds | 30000 (30 seconds) |
data | Allows you to set a default value for data | undefined |
loading | Allows you to set default value for loading | false unless onMount === true |
interceptors.request | Allows you to do something before an http request is sent out. Useful for authentication if you need to refresh tokens a lot. | undefined |
interceptors.response | Allows you to do something after an http response is recieved. Useful for something like camelCasing the keys of the response. | undefined |
useFetch({
url: 'https://example.com',
onMount: true,
onUpdate: []
onTimeout: () => {},
onAbort: () => {},
retries: 3,
timeout: 10000,
data: [],
loading: false,
interceptors: {
request: async (options) => {
return options
},
response: (response) => {
return response
}
}
})
Does your company use use-http? Consider sponsoring the project to fund new features, bug fixes, and more.

Feature Requests/Ideas
If you have feature requests, let's talk about them in this issue!
Todos
const request = useFetch('https://example.com')
request
.headers({
auth: jwt
})
.query({
no: 'way'
})
.get()
<Provider responseKeys={{ case: 'camel' }}><App /></Provider>
const request = useFetch({
retryOn: [503],
retryOn(attempt, error, response) {
if (error !== null || response.status >= 400) {
console.log(`retrying, attempt number ${attempt + 1}`);
return true;
}
},
onServer: true,
query: `some graphql query`
mutation: `some graphql mutation`
})
const request = useQuery({ onMount: true })`your graphql query`
const request = useFetch(...)
const userID = 'some-user-uuid'
const res = await request.query({ userID })`
query Todos($userID string!) {
todos(userID: $userID) {
id
title
}
}
`
const request = useFetch('https://url.com', globalOptions => {
delete globalOptions.headers.Authorization
return globalOptions
})
The Goal With Suspense (not implemented yet)
import React, { Suspense, unstable_ConcurrentMode as ConcurrentMode, useEffect } from 'react'
function WithSuspense() {
const suspense = useFetch('https://example.com')
useEffect(() => {
suspense.read()
}, [])
if (!suspense.data) return null
return <pre>{suspense.data}</pre>
}
function App() (
<ConcurrentMode>
<Suspense fallback="Loading...">
<WithSuspense />
</Suspense>
</ConcurrentMode>
)
GraphQL with Suspense (not implemented yet)
const App = () => {
const [todoTitle, setTodoTitle] = useState('')
const mutation = useMutation('http://example.com', `
mutation CreateTodo($todoTitle string) {
todo(title: $todoTitle) {
id
title
}
}
`)
const createTodo = () => mutation.read({ todoTitle })
if (!request.data) return null
return (
<>
<input onChange={e => setTodoTitle(e.target.value)} />
<button onClick={createTodo}>Create Todo</button>
<pre>{mutation.data}</pre>
</>
)
}