use-http-request-hook v1.2.6
use-http-request-hook
Utility to aid in reducing Redux boilerplate for API queries by creating an easy to use interface for requesting data. Functions similarly to rtk-query albeit with various differences. Handles the following:
- Data fetch caching and cache update
- Data fetch predicates
- Handling concurrent requests
- Data synchronization between tabs
- Request cancellation with signal
- Lifecycle methods
- Overriding data
- Resetting data
Prerequisites
Usage of the library requires the following dependencies: 1. React 16+ 2. Redux 7+
Installation
To install the library, run the following command:
npm install --save use-http-request-hook
Setup
In order to get started, we need to register the reducer:
import { combineReducers } from 'redux';
import useRequestReducer, {
reducerName as useRequestReducerName,
} from 'use-http-request-hook/lib/reducer';
export default combineReducers({
[useRequestReducerName]: useRequestReducer,
});
Global Configuration
Because various HTTP clients (i.e. axios
) may return data differently, configuration of a global response
resolver can be done:
import { configure } from 'use-http-request-hook'
configure.setResponseResolver((response) => {
return response.data;
});
In the above, we expect that when useRequest
is invoked, the request
parameter function will return data nested
under the data
fields. For example, with function such () => axios.get('https://google.com');
the data returned
would appear as such:
{
"data": ...
}
In this case, we configure the global response resolver to resolve and return data under the .data
path.
Basic Usage
Once configured, the entrypoint to using the library is through the useRequest
function which can be imported
as follows:
import useRequest from 'use-http-request-hook';
const useUsers = () =>
useRequest(`users`, { // 'users' is the redux dataPath
request: () => axios.get('https://example.com/api/v1/users') // the request function is called and will query the api url
});
const { isLoading, data } = useUsers();
In the above, data will be stored under the users
redux path, and will query https://example.com/users
when ran. See
below for more information about input requirements and returned values.
Input Parameters
The useRequest
function takes in 2 parameters: a dataPath
and a configuration. The data path is required
as a unique key for storage in the global Redux store. The minimum configuration requires a request
field, containing
a function that returns any value (as seen in the above Basic Usage example).
Configuration Fields
All fields that can be passed via the configuration to useRequest
.
Name | Type | Default | Description |
---|---|---|---|
request | (signal) => any or (signal) => Promise | undefined | Callback function will be triggered, when cache time expires, or dataPath changes (dataPath should be string) |
isFetch | boolean | true | Whether to call config.request method |
cacheTime | number | 2*60*1000 | Cache time in milliseconds |
cacheTimeOnError | number | 200 | In order to prevent subsequent requests on fail we have cacheTimeOnError in milliseconds, when fetch was not successful |
onError | (responseError) => void | undefined | Callback function for handling error when config.request method fails |
isSyncAction | boolean | false | Will synchronize fetched data between different tabs (Requires Integration with redux-state-sync . see docs. below) |
isPersist | boolean | false | Will try to take data from localStorage and set it initially. Data still will be fetched for invalidation |
appProgress | boolean | false | Will add appProgress for redux action meta |
Note: Bolded fields are required.
Output Parameters
Calling the useRequest
function will return an object containing the following fields:
Name | Type | Default | Description |
---|---|---|---|
isLoading | boolean | undefined | Data loading status |
data | any | undefined | Returned data from config.request callback |
error | any | undefined | Returned error from config.request callback |
getData | Can be used to re-fetch data manually (It will call config.request callback) | ||
setData | We can set data manually (setData({ path, data }) . By default path will be equal to dataPath (Unique path key to data stored in global store)) | ||
resetData | We can reset data to default (resetData({ path }) or resetData() . By default path will be equal to dataPath (Unique path key to data stored in global store). We can also pass a function resetData((store) => path[]) . Function will accept store and should return array of paths) | ||
lastFetchedAt | timestamp | undefined | Last time the data was refreshed |
Examples
Handling concurrent requests
In this example there will be only one request made in case equal userId
.
It checks by dataPath
parameter, and will ignore all subsequent requests with the same dataPath
.
const useUser = ({ userId }) =>
useRequest(`users:${userId}`, {
request: () => axios.get(`https://example.com/api/v1/users/${userId}`),
isFetch: userId
});
const ComponentA = ({ userId }) => {
const { userId } = props;
const { isLoading, data } = useUser({ userId });
return (
<Spinner isLoading={isLoading}>
// ...
</Spinner>
);
};
const ComponentB = ({ userId }) => {
const { userId } = props;
const { isLoading, data } = useUser({ userId });
return (
<Spinner isLoading={isLoading}>
// ...
</Spinner>
);
};
const MainComponent = (props) => (
<>
<ComponentA userId={props.userId} />
<ComponentB userId={props.userId} />
</>
)
Override data caching
By default, data is cached for 2 minutes. To override this value, the cacheTime
variable can be specified.
const useUsers = () =>
useRequest(`users`, { // 'users' is the redux dataPath
request: () => axios.get('https://example.com/api/v1/users'), // the request function is called and will query the api url
cacheTime: 1000 // 1000ms
});
Disabling the cache can be done by specifying a value of 0
for cacheTime
.
Predicated fetching
In certain cases, we may not want to fetch data until a prerequisite value is fetched. Consider the case where we want to fetch notes for a user based on his ID, but we only have his username to start with. In a case like this we need to load the users ID first before making the call.
const getNotes = ({ userId }) =>
useRequest(`users/${userId}/notes`, {
request: () => axios.get(`https://example.com/api/v1/users/${userId}/notes`),
isFetch: userId // trigger the fetch only when userId is available
});
By configuring an isFetch
predicate, calls to the getNotes
function won't trigger unless the userId
value
exists.
Showing a loading indicator
In some contexts of a React application, you may want to show a loading indicator until data is retrieved
from a server. This is made easy with the isLoading
value returned by the function.
const useUser = ({ userId }) =>
useRequest(`users:${userId}`, {
request: () => axios.get(`https://example.com/api/v1/users/${userId}`),
isFetch: userId
});
const User = (props) => {
const { userId } = props;
const { isLoading, data } = useUser({ userId });
return (
<Spinner isLoading={isLoading}>
// ...
</Spinner>
);
}
Integrating with redux-state-sync
To set up an integration with the redux-state-sync library, we need configure and register the provided redux middleware.
import { applyMiddleware, createStore, compose } from 'redux';
import { createStateSyncMiddleware, initStateWithPrevTab } from 'redux-state-sync';
const syncConfig = {
broadcastChannelOption: { type: 'localstorage' },
predicate: (action) => action.meta && action.meta.isSyncAction,
};
const middlewares = [
// Any custom redux middlware
createStateSyncMiddleware(syncConfig),
];
const store = createStore(reducers, compose(applyMiddleware(...middlewares)));
initStateWithPrevTab(store);
export default store;
In the above, we define a predicate where actions must have the meta of isSyncAction
to synchronize. With this in
mind, we need to specify this parameter when performing calls with useRequest
. Usage would look like:
const useUser = ({ userId }) =>
useRequest(`users:${userId}`, {
request: () => axios.get(`https://example.com/api/v1/users/${userId}`),
isFetch: userId,
isSyncAction: true
});
Request cancellation with AbortController
Starting from version 1.2.0
request callback function gets as argument signal
from AbortController.
This signal
will be invoked once hook will be unmounted. It will give developers ability to cancel all api requests.
AbortController works with fetch and axios starting from version 0.22.0
.
With Axios
const useUser = ({ userId }) =>
useRequest(`users:${userId}`, {
request: (signal) => axios.get(`https://example.com/api/v1/users/${userId}`, { signal }),
});
With Fetch
const useUser = ({ userId }) =>
useRequest(`users:${userId}`, {
request: (signal) => fetch(`https://example.com/api/v1/users/${userId}`, { signal }),
});