Middleware that gives you a uniform way to define API actions in Redux applications.
Example usage:
import { API_ACTION_TYPE } from 'redux-rest-api'
store.dispatch({
[API_ACTION_TYPE]: {
types: ['PENDING', 'SUCCESS', 'FAILURE'],
endpoint: 'https://monsterfactory.net',
fetchOptions: {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({
name: 'Guy Fieri'
})
}
}
}).then(() => {
console.log()('Oh no what have you done')
}).catch(() => {
console.log('Thank god')
})
npm install redux-rest-api
In order for a fetch to request to be generated whenever an API action is dispatched, the api middleware must be applied to your store.
// Adding the api middleware to your store
import { apiMiddleware } from 'redux-rest-api'
import { applyMiddleware, createStore } from 'redux'
const middleware = applyMiddleware(apiMiddleware)
const store = createStore(reducers, initialState, middleware)
Now whenever an API action like this gets dispatched to the store:
// Example API action
import { API_ACTION_TYPE } from 'redux-rest-api'
const promise = store.dispatch({
[API_ACTION_TYPE]: {
types: ['PENDING', 'SUCCESS', 'FAILURE'],
endpoint: 'https://monsterfactory.net',
fetchOptions: { /* Options to pass to underlying fetch request */ }
}
})
// Do something with the returned Promise if you want to, but you don't have to.
A fetch request will be made to the given endpoint
, and three actions with the specified types
will be dispatched while that fetch request is being made.
The
You can specify the types of these three actions by setting the types
property of the api action to an array of three strings.
The first string will be the pending
action type, the second the success
action type, and the third the failure
action type.
The pending
action is dispatched to the store before the fetch request is created.
// Pending action
{
type: ['PENDING']
}
This action will be dispatched to the store if the fetch request resolves with an OK
status code. The action's payload will contain the JSON response from the endpoint.
// Success action
{
type: ['SUCCESS'],
payload: { /* Server response as JSON */ }
}
The failure action is dispatched to the store if the fetch request is rejected with a non-OK
status code. The payload of the action will contain the Response object from the rejected request.
// Failure action
{
type: ['FAILURE'],
payload: { /* Response object */ }
}
Now that you have middleware that is generating actions for you, you need to create a reducer that will handle those actions and update the store accordingly.
You can use the reducer included with redux-rest-api
to handle these actions, or you can write your own.
Odds are the state for most of your API requests is going to look very similar:
// Example API request state
const state = {
// True if a request is in progress
isPending: false,
// Contains a response from an API if a request was successful.
response: null,
// Contains the failed [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object from a rejected request.
error: null
}
As such, redux-rest-api
provides a reducer that you can use to manage the state of multiple API requests.
// Setting up reducers to handle the state for multiple API requests within an application
import { configureApiReducer } from 'redux-rest-api'
import { combineReducers } from 'redux'
const reducers = combineReducers({
// Create a reducer that will respond to API actions with the specified `types`.
monsters: configureApiReducer({
// The types for this API action.
types: ['MONSTERS_FETCH_PENDING', 'MONSTERS_FETCH_SUCCESS', 'MONSTERS_FETCH_FAILURE']
}),
pokemon: configureApiReducer({
// The types for this API action.
types: ['POKEMON_FETCH_PENDING', 'POKEMON_FETCH_SUCCESS', 'POKEMON_FETCH_FAILURE']
}),
...
})
export reducers
The initial state of the store will look like this:
// Initial state
{
monsters: {
isPending: false,
response: null,
error: null
},
pokemon: {
isPending: false,
response: null,
error: null
}
}
The api reducer will handle actions from the API middleware that match the given types
.
The reducer will set the isPending
property to true.
// State after receiving a pending action
{
isPending: false => true
}
The api reducer will set the isPending
property to false
and will populate the response
field with the JSON
response from the API. error
will be set to null.
// State after receiving a success action
{
isPending: true => false,
response: null => { /* JSON response */ },
error: { /* Previous error object */ } => null
}
The api reducer will set the isPending
property to false
and will populate the error
field
with the Response object from the failed fetch request.
// State after receiving a failure action
{
isPending: true => false,
error: null => { /* Response object from failed fetch request */ }
}
If the provided reducer doesn't meet your needs, you can always write your own reducer to handle the actions that are dispatched by the API middleware.
Check out the source for the included reducer for an example.
Once you have a reducer to handle the actions dispatched by the API middleware, you can start dispatching API actions.
API actions have the following properties:
This is an array of three strings that define the action types that will be dispatched by the API middleware.
The url for the fetch request.
An object containing the options that will be passed to the underlying fetch request, such as body
and method
.
For a full list of options you can pass to the fetch request, check out out the MDN docs.
Because the API middleware converts API actions to Promises, you can delay rendering a component until the data that it requires has been fetched. This is super handy for server side rendering.
Here's an example of delaying rendering with React Router:
// Example of delaying rendering until data has been fetched
import { API_ACTION_TYPE } from 'redux-rest-api'
// Route configuration that has been given access to the store.
// Example: routeConfig(store): route configuration
<Route path="/" component={SomeComponent} onEnter={(state, replace, next) => {
// Dispatch an action and wait for it to resolve (or be rejected) before rendering.
store.dispatch({
[API_ACTION_TYPE]: {
types: ['FIERI_DESTROY_PENDING', 'FIERI_DESTROY_SUCCESS', 'FIERI_DESTROY_FAILURE'],
endpoint: 'http://monsterfactory.net/1',
fetchOptions: {
method: 'DELETE'
}
}
}).then(() => {
// Store state has been updated π, render the component
next()
}).catch(() => {
// Uh oh, something broke :/, maybe dispatch an action to render an alert?
next()
})
}} />
For more details on async routes in React Router, check out the React Router docs.
MIT