|
[[api-middleware-intro]]
|
|
|
|
= API Middleware Usage
|
|
:toc: right
|
|
:toclevels: 5
|
|
|
|
# API Middleware
|
|
|
|
Instead of each component handling API calls in the same way we have the API Middleware that will handle it.
|
|
|
|
## How to use API in a Component using useAPI hook
|
|
|
|
The API middleware is abstracted by the `useAPI` custom hook.
|
|
|
|
```js
|
|
import { useAPI } from '../common/hooks/API';
|
|
import KEY_FOR_API from './consts';
|
|
import { successCallback, errorCallback } from './helper';
|
|
|
|
const MyComponent = () => {
|
|
const options = {
|
|
handleSuccess: successCallback,
|
|
handleError: error => (error.response.status === 401 ? logoutUser() : null),
|
|
successToast: response => 'This text will be shown as a toast after a success call',
|
|
errorToast: response => 'This text will be shown as a toast when error occurs',
|
|
};
|
|
const {
|
|
response: { results },
|
|
status, // The current status of the API call
|
|
key, // Generated key for storing in redux's store
|
|
setAPIOptions, // Function to update the options and make a new api call
|
|
} = useAPI('get', '/api/hosts', options);
|
|
return (
|
|
<ul>
|
|
<button onClick={() => setAPIOptions({ ...options, params: { search: 'os=fedora' } })}>
|
|
Fedora
|
|
</button>
|
|
<button onClick={() => setAPIOptions({ ...options, params: { search: 'os=debian' } })}>
|
|
Debian
|
|
</button>
|
|
{results.map(item => (
|
|
<li key={item.id}>{item.title}</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
};
|
|
```
|
|
|
|
## How to use the API middleware
|
|
|
|
The api middleware is a redux middleware that handles API calls in the application.
|
|
It is recommended to use the `useAPI` hook instead of using the middleware directly.
|
|
|
|
|
|
```js
|
|
/** MyComponent.js*/
|
|
import React from 'react';
|
|
import { STATUS } from '../../constants';
|
|
|
|
const MyComponent = ({ status, error, items }) => {
|
|
if (status === STATUS.PENDING) {
|
|
return <div>Loading...</div>;
|
|
}
|
|
|
|
if (status === STATUS.ERROR) {
|
|
return <div>Error: {error.message}</div>;
|
|
}
|
|
|
|
return (
|
|
<ul>
|
|
{items.map(item => (
|
|
<li key={item.id}>
|
|
{item.title} {item.action}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
};
|
|
```
|
|
|
|
```js
|
|
/** index.js*/
|
|
import React, { useEffect } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { useSelector, useDispatch } from 'react-redux';
|
|
import {
|
|
selectItems,
|
|
selectStatus,
|
|
selectError,
|
|
} from './MyComponentsSelectors.js';
|
|
import { getData } from './MyComponentActions';
|
|
import MyComponent from './MyComponent';
|
|
|
|
const ConnectedMyComponent = ({ path }) => {
|
|
const items = useSelector(selectItems);
|
|
const status = useSelector(selectStatus);
|
|
const error = useSelector(selectError);
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
useEffect(() => {
|
|
dispatch(getData(path));
|
|
}, [path]);
|
|
|
|
return <MyComponent items={items} status={status} error={error} />;
|
|
};
|
|
|
|
ConnectedMyComponent.propTypes = {
|
|
path: PropTypes.string.isRequired,
|
|
};
|
|
|
|
export default ConnectedMyComponent;
|
|
```
|
|
|
|
### Access the API store
|
|
|
|
We provided you the `selectAPIByKey` in '/APISelectors.js' which will return the key substate,
|
|
there are also `selectAPIStatus`, `selectAPIPayload`, `selectAPIResponse`, `selectAPIError` and `selectAPIErrorMessage`.
|
|
```js
|
|
/** MyComponentSelectors.js*/
|
|
|
|
import { MY_SPECIAL_KEY } from './MyComponentConstants';
|
|
import {
|
|
selectAPIStatus,
|
|
selectAPIError,
|
|
selectAPIResponse,
|
|
} from '../../redux/API/APISelectors';
|
|
|
|
// use the same key that you've used in the API action.
|
|
export const selectItems = state =>
|
|
selectAPIResponse(state, MY_SPECIAL_KEY).items || [];
|
|
|
|
export const selectStatus = state => selectAPIStatus(state, MY_SPECIAL_KEY);
|
|
|
|
export const selectError = state => selectAPIError(state, MY_SPECIAL_KEY);
|
|
```
|
|
|
|
Then there will be called 2 actions: **MY_SPECIAL_KEY_REQUEST** and **MY_SPECIAL_KEY_SUCCESS/ MY_SPECIAL_KEY_FAILURE**:
|
|
**MY_SPECIAL_KEY_REQUEST** will have the payload only
|
|
**MY_SPECIAL_KEY_SUCCESS** will have the payload and the return data from the API call.
|
|
**MY_SPECIAL_KEY_FAILURE** will have the payload and the return error from the API call.
|
|
|
|
In the **payload** field you should send any headers and params for the GET request, and any other data you want for the action.
|
|
|
|
The actions types can be changed with the optional **actionTypes** parameter:
|
|
|
|
```js
|
|
|
|
/** MyComponentActions.js*/
|
|
|
|
export const getData = url => ({
|
|
type: API_OPERATIONS.GET,
|
|
payload: {
|
|
key: MY_SPECIAL_KEY, // you will need to re-use this key in order to access the right API reducer later.
|
|
url,
|
|
payload: {
|
|
page: 2,
|
|
per_page: 10,
|
|
},
|
|
actionTypes: {
|
|
REQUEST: 'CUSTOM_REQUEST',
|
|
}
|
|
},
|
|
});
|
|
```
|
|
|
|
The example API returns a JSON object like this:
|
|
|
|
```json
|
|
{
|
|
"items": [
|
|
{ "id": 319, "title": "setting", "action": "update" },
|
|
{ "id": 150, "title": "bookmark", "action": "create" }
|
|
]
|
|
}
|
|
```
|
|
Once the action is triggered, the API middleware will manage the request
|
|
and update the store with the request status:
|
|
|
|
the store on API pending:
|
|
|
|
```js
|
|
{
|
|
...
|
|
API: {
|
|
MY_SPECIAL_KEY: { // The key that was provided in the API action.
|
|
response: null,
|
|
status: "PENDING",
|
|
payload: {},
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
the store on API success:
|
|
|
|
```js
|
|
{
|
|
...
|
|
API: {
|
|
MY_SPECIAL_KEY: {
|
|
response: {
|
|
items: [
|
|
{id: 319, title: "setting", action: "update"},
|
|
{id: 150, title: "bookmark", action: "create"}
|
|
],
|
|
},
|
|
status: "RESOLVED",
|
|
payload: {},
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
the store on API failure:
|
|
|
|
```js
|
|
{
|
|
...
|
|
API: {
|
|
MY_SPECIAL_KEY: {
|
|
response: "Network Error",
|
|
status: "ERROR",
|
|
paylod: {},
|
|
}
|
|
}
|
|
}
|
|
```
|