use-async-call
Version:
Provides a hook to provide state around an async function call
301 lines (239 loc) • 7.51 kB
Markdown

[](https://codecov.io/gh/azmenak/use-async-call)
[](https://travis-ci.org/azmenak/use-async-call)

Provides an abstraction over the lower-level [`use-async-reducer`](https://github.com/azmenak/use-async-reducer), handles calls to `useEffect` and handles cancelation when the inputs change or components unmounts to avoid modifying stale data
```
npm install use-async-call
```
```ts
import useAsyncCall from 'use-async-call'
const [state, {update, refresh, actions}] = useAsyncCall(
asyncCreator,
(options = {})
)
```
[](https://codesandbox.io/s/mo51y75rzx?fontsize=14)
- `asyncCreator` An async method (returns a promise), create this method with `useCallback` if it depends on state from the component
```ts
const [searchText, setSearchText] = useState('')
const fetchData = useCallback(() => Api.search(searchText), [searchText])
```
- `options`
```ts
interface UseAsyncCallOptions<T> {
/**
* Initial value used for `data` of state
*/
initialValue?: T
/**
* When true, will not call `actions.initalize` when `asyncCreator` updates
* This keeps the data in the store between updates, useful when the identity
* of the data does not belong to the inputs, example would be a search
* component that uses "search text" as an input
*/
dontReinitialize?: boolean
/**
* Callback called after call is successful
* @param data Data returned from async caller
*/
onSuccess?(data?: T): void
/**
* Callback called after async call throws
* @param error Error thrown by async caller
*/
onFailure?(error?: Error): void
/**
* Callback always called after async call completes
*/
onComplete?(): void
}
```
- `state` an object containing state of async call
```ts
const state: Loadable = {
data: {}, // any data
loading: false, // true when calls in progress
error: null // instance of Error if calls throw
}
```
- `update(asyncUpdater, updateOptions = {})` method used to update the state
- `asyncUpdater` either a promise or a method which returns a promise, the result will be set to the `data` value of the state
- `updateOptions`
```ts
interface UseAsyncCallUpdateOptions<T> {
/**
* Should thrown errors be re-thrown in the resulting promise from `update`;
* useful when using in conjuction with form libraries that expect errors
* when submitting form values
*/
throwError?: boolean
/**
* If the caller throws, sets `state.error` to the error and `state.data` to
* `null`
*/
saveError?: boolean
/**
* Callback called after call is successful
* @param data Data returned from async caller
*/
onSuccess?(data?: T): void
/**
* Callback called after async call throws
* @param error Error thrown by async caller
*/
onFailure?(error?: Error): void
/**
* Callback always called after async call completes
*/
onComplete?(): void
}
```
- `refresh` method used to re-call the method passed to `useAsyncCall`
- `actions` action methods created by [`use-async-reducer`](https://github.com/azmenak/use-async-reducer)
```ts
interface AsyncReducerBoundActions<T = any> {
/**
* To be called at the beginning of a request, sets `loading` to `true`
*/
request(): void
/**
* To be called with the data to be saved into the state
* @param payload Result of the async call
*/
success(payload: T): void
/**
* To be called when the async call fails
* @param error
*/
failure(error: Error): void
/**
* Can be called when a call fails/complete and the result is being discarded
*/
complete(): void
}
```
```tsx
import React, {useCallback} from 'react'
import useAsyncCall from 'use-async-call'
import Api from './custom-api'
const DataLoadingComponent: React.FC<{id: number}> = ({id}) => {
const fetchData = useCallback(() => Api.fetchModelData(id), [id])
const [model] = useAsyncCall(fetchData)
// model is now managed, it will automatically fetch new data when `id` prop
// changes and update the state to reflect any changes
}
```
```tsx
import React, {useCallback} from 'react'
import useAsyncCall from 'use-async-call'
import Api from './custom-api'
interface User {
id: number
name: string
}
const UserProfile: React.FC<{userId: number}> = ({userId}) => {
const [name, setName] = useState('')
const fetchUser = useCallback(() => Api.fetchUserById(userId), [userId])
const [user, {update: updateUser}] = useAsyncCall<User>(fetchUser)
const handleUpdateUserName = useCallback((): Promise<User> => {
return Api.updateUser(userId, {name})
}, [userId, name])
return (
<div>
<h1>User: {userId}</h1>
{user.loading && <div>Loading...</div>}
{user.data && <div>{user.data.name}</div>}
{user.error && <div>{user.error.message}</div>}
{user.data && (
<>
<input
value={name}
onChange={(event) => setName(event.target.value)}
/>
<button
disabled={user.loading}
onClick={() => updateUser(handleUpdateUserName)}
>
Update Name
</button>
</>
)}
</div>
)
}
```
```ts
import {useCallback} from 'react'
import useAsyncCall, {Loadable} from 'use-async-call'
import Api from './custom-api'
interface User {
id: number
name: string
}
export function useUserData(
userId: number
): [Loadable<User>, (userData: Partial<User>) => Promise<User>] {
const fetchUser = useCallback(() => Api.fetchUserById(userId), [userId])
const [user, {update: updateUser}] = useAsyncCall<User>(fetchUser)
}, [])
const handleUpdateUser = useCallback(
(userData: Partial<User>) => {
return updateUser(Api.updateUser(userId, userData), {
onSuccess() {
alert('Updated user!')
},
onFailure() {
alert('Failed to update user')
}
})
},
[]
)
return [user, handleUpdateUser]
}
```
```tsx
import React, {useCallback, useState} from 'react'
import useAsyncCall from 'use-async-call'
import SearchApi from './search-api'
export function useSearchData(searchText: string) {
const fetchData = useCallback(() => SearchApi.find(searchText), [searchText])
return useAsyncCall(fetchData, {dontReinitialize: true})
}
const SearchComponent: React.FC = () => {
const [searchText, setSearchText] = useState('')
const [searchData] = useSearchData(searchText)
return (
<>
<input
value={searchText}
onChange={(event) => {
setSearchText(event.target.value)
}}
/>
{searchData.data && (
<>
<h1>Search Results</h1>
<ul>
{searchData.data.map((searchResult) => (
<li key={searchResult.id}>{searchResult.name}</li>
))}
</ul>
</>
)}
</>
)
}
```