use-async-call
Provides an abstraction over the lower-level use-async-reducer, handles calls to useEffect and handles cancelation when the inputs change or components unmounts to avoid modifying stale data
Install
npm install use-async-call
Usage
import useAsyncCall from 'use-async-call' const [state, {update, refresh, actions}] = useAsyncCall( asyncCreator, (options = {}) )
Demo
Params
asyncCreatorAn async method (returns a promise), create this method withuseCallbackif it depends on state from the component
const [searchText, setSearchText] = useState('') const fetchData = useCallback(() => Api.search(searchText), [searchText])
options
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 }
Return values
statean object containing state of async call
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 stateasyncUpdatereither a promise or a method which returns a promise, the result will be set to thedatavalue of the stateupdateOptions
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 }
-
refreshmethod used to re-call the method passed touseAsyncCall -
actionsaction methods created byuse-async-reducer
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 }
Examples
Basic Example
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 }
A component which updates a value at an API
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> ) }
Create a custom hook to load and update a model
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') } }) }, [userId] ) return [user, handleUpdateUser] }
Get Data from a Search API
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> </> )} </> ) }