Releases: ava/use-http
Removal of "path" and "url" fields
- this removes the
path
andurl
fields in exchange for the 1st argument handling both. #247
🚨 BREAKING 🚨
useFetch('/path')
// AND
useFetch('https://overwrite-global-url.com')
Scalable Interceptor Arguments, responseType option
Scalable Interceptor Arguments
🚨🚨THIS RELEASE CONTAINS BREAKING CHANGES!!! 🚨🚨
Due potential future scalability issues such as adding new items to pass to the interceptors, we're changing it to an object destructuring syntax instead of multiple arguments.
useFetch({
interceptors: {
request: async (options, url, path, route) => {},
response: async (response) => {}
}
}
will now be
useFetch({
interceptors: {
request: async ({ options, url, path, route }) => {}, // <- notice object destructuring
response: async ({ response }) => {} // <- notice object destructuring
}
}
This provides a few benefits.
- we can now add new options to these callbacks without having breaking changes
- we can pull out these options in whatever order so we don't have unused variables
For example, let's say in the future we want to do
useFetch({
interceptors: {
request: async ({ options, route, attempt }) => {
// now we can add a new field `attempt`
// and we can just grab `route` without `url` and `path`
},
response: async ({ response, attempt }) => {
// now we can add a new field `attempt`
}
}
}
responseType
option
This will determine how the data
field is set. If you put json
then it will try to parse it as JSON. If you set it as an array, it will attempt to parse the response
in the order of the types you put in the array. Read about why we don't put formData
in the defaults in the yellow Note part here.
Defaults to: ['json', 'text', 'blob', 'readableStream']
useFetch({
// this would basically call `await response.json()`
// and set the `data` and `response.data` field to the output
responseType: 'json',
// OR can be an array. It's an array by default.
// We will try to get the `data` by attempting to extract
// it via these body interface methods, one by one in
// this order. We skip `formData` because it's mostly used
// for service workers.
responseType: ['json', 'text', 'blob', 'arrayBuffer'],
})
Version 1.0
Finally Version 1.0
Lots of upgrades in this version. This new major release is v1.0.2
on npm.
Updates
- Retry Functionality.
retries
,retryDelay
, andretryOn
- Suspense(experimental) Support.
suspense
- Persistent Caching.
persist
- Added
cache
to return ofuseFetch
for better cache debugging and to clear the cache for instance when a user signs out. - Fixed infinite loop/extra rendering issues when passing useFetch variables to useEffect dependencies #228 #185
- Fixed
response interceptor
not firing when results loaded from cache #235 - Can now have
[]
and''
as body of http request. Caveat, if posting a string as the body, must have a routepost('/', 'your body')
- Added
async
support forinterceptors.response
#214 - Remove
content-type: application/json
when posting formData #213 - Promise.all fixes #211
- Fixed
cannot perform update on unmounted component
bug #207 - Should now work in TypeScript apps made using parcel #202
- Lot's of videos added!
Pagination + Moving to dependency array for onMount + onUpdate
🚨⚠️ This release has breaking changes! ⚠️ 🚨
- removed
onMount
andonUpdate
in exchange for accepting a dependency array as the last argument ofuseFetch
- added
onNewData
to be used for merging new fetched data with current data for pagination
onMount
AND onUpdate
Pagination example
import useFetch, { Provider } from 'use-http'
const Todos = () => {
const [page, setPage] = useState(1)
const { data, loading } = useFetch({
path: `/todos?page=${page}&amountPerPage=15`,
onNewData: (currTodos, newTodos) => [...currTodos, ...newTodos], // appends newly fetched todos
data: []
}, [page]) // runs onMount AND whenever the `page` updates (onUpdate)
return (
<ul>
{data.map(todo => <li key={todo.id}>{todo.title}</li>}
{loading && 'Loading...'}
{!loading && (
<button onClick={() => setPage(page + 1)}>Load More Todos</button>
)}
</ul>
)
}
const App = () => (
<Provider url='https://example.com'>
<Todos />
</Provider>
)
onMount
only example
import useFetch, { Provider } from 'use-http'
function Todos() {
const { loading, error, data } = useFetch({
path: '/todos',
data: []
}, []) // onMount
return (
<>
{error && 'Error!'}
{loading && 'Loading...'}
{data.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}
const App = () => (
<Provider url='https://example.com'>
<Todos />
</Provider>
)
onUpdate
only example:
If for some reason you need to only do onUpdate
without onMount
, you will have to use managed state for this.
import useFetch, { Provider } from 'use-http'
function Todos() {
const { loading, error, get, data } = useFetch({
path: '/todos',
data: []
})
const mounted = useRef(false)
useEffect(() => {
if (mounted.current) {
// onUpdate only
get()
}
mounted.current = true
})
return (
<>
{error && 'Error!'}
{loading && 'Loading...'}
{data.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}
const App = () => (
<Provider url='https://example.com'>
<Todos />
</Provider>
)
Support for a Provider, useMutation, and useQuery
Provider
, useMutation
, and useQuery
Provider
using the GraphQL useMutation
and useQuery
The Provider
allows us to set a default url
, options
(such as headers) and so on.
There is array and object destructuring for useMutation
and useQuery
.
Query for todos
import { Provider, useQuery, useMutation } from 'use-http'
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
This uses array destructuring, but it can also use object destructuring. The useMutation
and useQuery
are very similar to the usePost
's array and object destructuring.
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>}
</>
)
}
Fetch more todos
function FetchComponent() {
const request = useFetch('/todos')
return (
<>
<button onClick={request.get}>Get Todos</button>
{request.loading ? 'Loading...' : <pre>{request.data}</pre>}
</>
)
}
Provider
These props are defaults used in every request inside the <Provider />
. They can be overwritten individually
function App() {
const options = {
headers: {
Authorization: 'Bearer:asdfasdfasdfasdfasdafd'
}
}
return (
<Provider url='http://example.com' options={options}>
<QueryComponent />
<MutationComponent />
<FetchComponent />
<Provider/>
)
}
Abort Functionality
Abort
In this release, we've added abort functionality. Now you can call abort on any http request and it will cancel it. We decided not to allow aborting multiple requests at once. If the community decides that this is a feature we need to add, we will add it. If you want that, please comment on the Feature request and syntax ideas issue.
const githubRepos = useFetch({
baseUrl: `https://api.github.com/search/repositories?q=`
})
// the line below is not isomorphic, but for simplicity we're using the browsers `encodeURI`
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>
))}
</>
Fetching on-demand vs only in "componentDidMount"
Changing the original behavior from only fetching on mount to whenever you want.
Previous behavior:
import useFetch from 'use-http'
function App() {
// add whatever other options you would add to `fetch` such as headers
const options = {
method: 'POST',
body: {}, // whatever data you want to send
}
var [data, loading, error] = useFetch('https://example.com', options)
// want to use object destructuring? You can do that too
var { data, loading, error } = useFetch('https://example.com', options)
if (error) return 'Error!'
if (loading) return 'Loading!'
return (
<code>
<pre>{data}</pre>
</code>
)
}
this would only work when the component first rendered. Now we can do
import useFetch from 'use-http'
function App() {
// add whatever other options you would add to `fetch` such as headers
const options = {}
var [data, loading, error, request] = useFetch('https://example.com', options)
// want to use object destructuring? You can do that too
var { data, loading, error, request, get, post, patch, put, del } = useFetch('https://example.com')
const postData = () => {
post({
no: 'way',
})
// OR
request.post({
no: 'way',
})
}
if (error) return 'Error!'
if (loading) return 'Loading!'
return (
<>
<button onClick={postData}>Post Some Data</button>
<code>
<pre>{data}</pre>
</code>
</>
)
}
There's also support for
var [data, loading, error, request] = useFetch({
onMount: true, // will fire on componentDidMount
baseUrl: 'https://example.com'
})
const handleClick = () => {
request.post('/todos', {
id: 'someID',
text: 'this is what my todo is'
})
}
And don't forget
var { data, loading, error, patch } = usePatch({
url: 'https://example.com',
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
})