Request communication-state tracker that's pluggable into redux
.
Definition of communication-state in the scope of this library:
Communication state is the transitional state that a request communicating with an external source has to go through from initiation to completion. The transitionary sub-states of communication-state commonly include: in-flight (fetching/loading) failed-state (represented by an error) and success-state (represented by a successful response)
Redux-Communication removes the need for you to worry about boilerplate-heavy communication-state handling that is necessary to represent any request transitions in you redux-application by generalizing the following:
- Dispatching
requested
,error
andsuccess
actions - Storing
fetching
,error
andresponse
states in your redux-store - Creating the selector necessary to get the communication-state of any given request from your state
- Creating the action-creator needed to trigger your request
- Creating the action-types for the events occurring during a given request's life-cycle
By having all of these automatically generated and handled by the reducer-middleware-duo, your code is left with much less redux request-handling-boilerplate & you'll have much less pesky asynchronous code to test.
Install from the npm
registry
npm i redux-communication
Use createCommunication to create a [ middleware, request ] tuple and plug it into redux.
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { createCommunication } from 'redux-communication'
const [ communicationMiddleware, communicationReducer ] = createCommunication()
/*
* redux-communication makes the assumption that your communication-state
* is situated under the key 'communication' so make sure you map the
* returned communicationReducer to that key
*/
const reducer = combineReducers({ communication: communicationReducer })
const store = createStore(
reducer,
applyMiddleware(communicationMiddleware)
)
Use createRequest to create the following tuple
[ requestActionCreator, requestCommunicationSelector, requestActionTypes ]
import { createRequest } from 'redux-communication'
const dataRequest = createRequest(
'data',
id => fetch(`https://my-api.com/v1/data/${id}`),
)
/*
* export and use in a different file (for example from a React-Component
* connected with 'react-redux's 'connect()') to trigger the request in
* the middleware
*/
export const [ requestData ] = dataRequest
In this example requestData
is an action-creator that will trigger the actual call for the data to be executed.
The actual request-function requires an id
argument in this example, to make sure that the fetch-caller function receives that argument call & dispatch the requestData
action-creator with that argument as follows:
store.dispatch(requestData(42))
Or in react-redux' connect
mapDispatchToProps
callback:
export default connect(null, { requestData })(Component)
Selecting the Communication for a given request from your store-state
The second element in the tuple returned from createRequest is a selector that returns the Communication object for the created request.
import { createRequest } from 'redux-communication'
const dataRequest = createRequest(
'data',
() => fetch(endpoint),
)
const [ , dataCommunicationSelector ] = dataRequest
Use it to retrieve the the communication-state for the associated request.
const dataCommunication = dataCommunicationSelector(store.getState())
const { fetching, error, response } = dataCommunication
Or in react-redux' connect
mapStateToProps
callback:
const mapStateToProps = state => ({ dataCommunication: dataCommunicationSelector(state) })
export default connect(mapStateToProps)(Component)
If having only the Communication object available to you via the communicationSelector
is not enough to handle a particular request and you would prefer to trigger some imperative action either or completion or failure of any given request you can do so by hooking into the promise returned from the call to dispatch:
const dataRequest = createRequest(
'data',
() => fetch(endpoint),
)
const [ requestData ] = dataRequest
store.dispatch(requestData())
.then(/* handle success imperatively */)
.catch(/* handle error imperatively */)
Or from inside a react-component (assuming dispatch-wrapped action-creator injected through react-redux-connect
props):
componentDidMount () {
this.props.requestData()
.then(/* handle success imperatively */)
.catch(/* handle error imperatively */)
}
The third element in the tuple returned from createRequest is a RequestActionTypes object for the created request. You can use it to hook your own reducers up to the events of that request's communication-state.
import { createRequest } from 'redux-communication'
const dataRequest = createRequest(
'data',
() => fetch(endpoint),
)
const [ , , dataRequestActionTypes ] = dataRequest
const dataReducer = (state, action) => {
switch (action.type) {
case dataRequestActionTypes.succeeded: {
const { response } = action.payload
/*
* react to the data-request having succeeded you
* have access to action.payload.response here
*
* response being the value your promise resolved with
*/
}
}
}
() => [
requestMiddleware,
requestReducer,
]
Should be self-explanatory, see Installation guide for usage of this.
(namespace, request) => [
requestActionCreator,
requestCommunicationSelector,
requestActionTypes,
]
Used to create name-spaced action-types of this format @@communication [ namespace ] REQUESTED
Returns a promise
will receive whatever you pass into the action-creator returned from createRequest
Action-Creator
returns a redux-action
the arguments you pass into this are passed to the request
function (2nd argument of createRequest)
Selector function, returns a Communication object
{
requested: string
failed: string
succeeded: string
}
The action-types a given request uses to track its communication-state.
{
fetching: boolean
error: any
response: any
}
The fetching state of a given request-communication
The error your promise might throw (the promise that is returned from the request
argument in createRequest)
The value your promise might resolve to (the promise that is returned from the request
argument in createRequest)
This library is agnostic as can be about any other aspect of your application in order to cover as many edge-cases as possible.
It does for example not make assumptions about the technology you are using to make your requests ( can be axios
, can be fetch
, can even be jQ
).
Because your use-case might not be covered by simply storing the error/response in some place in the state this library exposes the generated action-types created for each request so you can hook into them.
Nothing is hidden away, everything passing through the communication-middleware is exposed and you can listen for it too!
MIT @ Matthias Margot matthiasmargot@hotmail.com