useAsync
useReducer hook's extension to manage asynchronous actions over application's state through http requests.
Use case
useReducer hook is very useful for managing application's state, but... What about if you want to integrate http requests on the state's actions? useAsync hook allows you to manage it in an easy way.
How?
Specify an http client with get, post, put and delete methods through the ClientStore. You can use axios, fetch or any other http client. See Client Store.
Define an actions object with this pattern for the common actions:
const actions = {
...
ACTION_TYPE: (state, payload) => ({
...state,
data: payload
}),
ASYNC_ACTION: (state) => ({
...state,
request: {
method: 'GET',
url: '/api/data'
}
})
...
}
Note: reducers are commonly defined through switch statements. Author's preference is to use always objects instead of those.
The request
key in the ASYNC_ACTION
type indicates this action is asynchronous. In this example, the client agent will execute a get
http call to the url /api/data
. Optionally, to manage the state when the request has been successfully executed add an action with the same type followed by _SUCCESS
; and to manage the state when the request has failed add an action with the same type followed by _ERROR
. See Actions, Dispatches and State.
Then, you only have to pass this actions objet to the useAsync
hook.
const [state, dispatch] = useAsync(actions, initialState)
Note: You can specify an optional initial state as a second argument. The default one is an empty object.
Installation
npm install --save @sonofjs/use-async
Usage
This is a very simple example showing how to use the useAsync
hook.
-
axios
is used as client agent - Example action types:
-
FETCH_DATA
: dispatched on theuseEffect
hook ofComponent
. It has arequest
key in the output of the action, which indicates the client agent that should make an http request. -
FETCH_DATA_SUCCESS
: will be dispatched if the http call throwed through theFETCH_DATA
action has been successfully executed. -
FETCH_DATA_ERROR
: will be dispatched if the http call throwed through theFETCH_DATA
action has failed. -
CREATE
: dispatched on the button click event. Will throw an http POST request. -
CREATE_SUCCESS
: will be dispatched if the http call throwed through theCREATE
action has been successfully executed. -
CREATE
: will be dispatched if the http call throwed through theCREATE
action has failed. -
UPDATE
: will throw an http PUT request. -
DELETE
: will throw an http DELETE request.
-
Note: the *_SUCCESS
and *_ERROR
actions are optionals.
Client store
Specify the client agent through the ClientStore
.
import React from 'react'
import { ClientStore } from '@sonofjs/use-async'
import axios from 'axios'
import Component from './Component.jsx'
const ViewContainer = () => (
<ClientStore.Provider agent={axios}>
<Component />
</ClientStore.Provider>
)
export default ViewContainer
Actions, dispatches and state
Define the state actions and use the useAsync
hook to manage it.
import React, { useEffect } from 'react'
import useAsync from '@sonofjs/use-async'
const actions = {
FETCH_DATA: (state) => ({
...state,
loading: true,
request: {
method: 'GET',
url: '/api/data'
}
}),
FETCH_DATA_SUCCESS: (state, response) => ({
...state,
loading: false,
data: response
}),
FETCH_DATA_ERROR: (state, error) => ({
...state,
loading: false,
error
}),
CREATE: (state, payload) => ({
...state,
request: {
method: 'POST',
url: '/api/data',
body: payload
}
}),
CREATE_SUCCESS: (state, response) => ({
...state,
data: response
}),
CREATE_ERROR: (state, error) => ({
...state,
error
}),
UPDATE: (state, payload) => ({
...state,
request: {
method: 'PUT',
url: '/api/data',
body: payload
}
}),
DELETE: (state, payload) => ({
...state,
request: {
method: 'DELETE',
url: '/api/data',
body: payload
}
})
}
const initialState = {
loading: false,
data: {}
}
const Component = () => {
const [state, dispatch] = useAsync(actions, initialState)
useEffect(() => {
dispatch({ type: 'FETCH_DATA' })
}, [])
const create = () => dispatch({ type: 'CREATE', payload: { name: '::name::' } })
return (
<>
{state.loading ? <span>Loading...</span> : null}
{<span>{JSON.stringify(state.data)}</span>}
{state.error ? <span>Error: {JSON.stringify(state.error)}</span> : null}
<button onClick={create}>Create</button>
</>
)
}
export default Component
Custom client agent
Interceptors and other methods can be used meanwhile the client agent is used by the asyncAction
hook. To connect the client to the store and use its state data implement a method named connect
in your client.
/* Your client agent */
import axios from 'axios'
const client = axios.create({
baseURL: '/api',
responseType: 'json'
})
const setToken = (config, { store = {} }) => {
const { token } = store
if (!token) return
config.headers.Authorization = `Token ${token}`
}
client.interceptors.request.use(config => {
setToken(config, client)
return config
})
client.connect = ({ state: { token } }) => {
client.store = { token }
}
export default client
...
/* Use of ClientStore */
import React from 'react'
import { ClientStore } from 'use-async'
import clientAgent from './clientAgent'
import Component from './Component.jsx'
const ViewContainer = () => (
<ClientStore.Provider agent={clientAgent}>
<Component />
</ClientStore.Provider>
)
export default ViewContainer
The connect
method will be fired on every state change.
Contributing
Contributions welcome; Please submit all pull requests the against master branch. If your pull request contains JavaScript patches or features, you should include relevant unit tests. Please check the Contributing Guidelines for more details. Thanks!
Author
Albert Pérez Farrés