@gggdomi/rrh
v0.3.4
Published
Helpers to remove boilerplate when doing common network operations with Redux.
Downloads
33
Maintainers
Readme
RRH aims to remove boilerplate when doing common network operations with Redux. It provides:
- helpers to trigger requests by dispatching actions, and dispatching actions when request resolve.
- reducer to automatically store requests state (loading, error), to use directly in components
- plugins
- RRH-auth: store credentials, authenticate requests and redirect to login if needed.
- RRH-alerts: display notifications when requests resolve.
Installation
RRH heavily relies on redux-saga and requires it to bet set up.
yarn add @gggdomi/rrh
Setup
Configure redux-saga
Configure your project to use redux-saga middleware (see redux-saga documentation). It'll probably look like:
// index.js
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
Run RRH sagas:
// index.js
import rrhSagas from '@gggdomi/rrh/src/sagas'
// configure saga middleware...
rrhSagas.map(sagaMiddleware.run)
Add RRH reducers:
// reducers.js
import { combineReducers } from 'redux'
import { rrhReducers } from '@gggdomi/rrh'
export const rootReducer = combineReducers({
your: yourReducer,
rrh: combineReducers(rrhReducers),
})
Usage
Define your routes:
// actions.js
import rrh from '@gggdomi/rrh'
const fetchUsers = rrh.new('FETCH_USERS', '/users/', {
storeData: true,
})
const createUser = rrh.new('CREATE_USER', '/users/new/', {
method: 'POST',
})
Start a request & use request state from your components:
// ExampleComponent.js
import React from 'react'
import { connect } from 'react-redux'
import { fetchUsers, createUser } from './actions'
const ExampleComponent = ({
loadingUsers,
errorMessage,
startFetchingUsers,
users,
startCreateUser,
creating,
userCreated,
}) => {
if (loadingUsers)
return "Loading..."
if (errorMessage)
return `Can't load users: ${errorMessage}`
return (
<div>
<h3>Users</h3>
<button onClick={startFetchingUsers}>Refresh</button>
<ul>
{users.map(x =>
<li key={x.id} />{x.firstName} {x.lastName}</li>
)}
</ul>
<button onClick={startCreateUser}>Create a user</button>
{creating && <div>Creating user...</div>}
{userCreated && <div>The user has been successfully created</div>}
</div>
)
}
const mapStateToProps = state => ({
loadingUsers: state.rrh.FETCH_USERS.loading,
errorMessage: state.rrh.FETCH_USERS.errorMessage,
users: state.rrh.FETCH_USERS.data,
creating: state.rrh.CREATE_USER.loading,
userCreated: state.rrh.CREATE_USER.success,
})
const mapDispatchToProps = dispatch => ({
startFetchingUsers: () => dispatch(fetchUsers.Start()),
startCreateUser: () => dispatch(createUser.Start({
data: { id: 123, firstName: 'John', lastName: 'Doe' }
})),
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(ExampleComponent)
Pre-defined action types available for your reducers:
// reducer.js
import { fetchUsers, createUser } from './actions'
const initialState = {
users: [],
createUserCount: 0,
cantCreateUser: false,
}
export default (state = initialState, action) => {
switch (action.type) {
case fetchUsers.SUCCESS:
return { ...state, users: action.data }
case createUser.START:
return { ...state, createUserCount: state.createUserCount + 1 }
case createUser.FAIL:
return { ...state, cantCreateUser: true }
default:
break
}
return state
}
Configuration
Global level
import rrh from '@gggdomi/rrh'
import rrhAuth from '@gggdomi/rrh-auth'
import rrhAlerts from '@gggdomi/rrh-alerts'
// base url to be pre-pendend to any route (default: null)
rrh.baseURL = "https://example.com/api"
// plugins
rrh.plugins = [rrhAuth, rrhAlerts]
//
rrh.getErrorMessage = formatError,
Route level
import rrh from '@gggdomi/rrh'
const createUser = rrh.new('CREATE_USER', '/users/new/', {
// method, forwarded to axios (default: 'GET')
method: 'POST',
// if we use an absolute url for this request and don't want to include rrh.baseURL (default: false)
ignoreBaseURL: true,
// automatically store request response data in reducer if set to true (default: false)
storeData: true,
// will be forwarded to axios via axios.request(...axiosOptions) (default: {})
axiosOptions: {
timeout: 60,
}
})
Call level
createUser.Start({
// POST data (default: null)
data: { id: 123, firstName: 'John', lastName: 'Doe' },
// appended to url (default: '')
// ex: will POST on /users/new/1234/
urlSuffix: '1234',
// id of the request, to be able to track network state for multiple requests of the same kind simultaneously (default: null, will generate a random reqId)
reqId: 1234,
})
Plugins
- rrh-auth: store credentials, authenticate requests and redirect to login if needed.
- rrh-alerts: display notifications when requests resolve.
Using a plugin
- Add plugin to RRH
import rrh from '@gggdomi/rrh'
import rrhAlerts from '@gggdomi/rrh-auth'
rrh.plugins = [rrhAlerts]
- Run plugin's sagas
import rrhAlertsSagas from '@gggdomi/rrh-alerts/src/sagas'
rrhAlertsSagas.map(sagaMiddleware.run)
See plugins' repositories for more informations
Creating your own plugin
There is two ways to plug into RRH behavior:
- By creating a saga that will take RRH's special actions
- By implementing
beforeRequest, enhanceStartAction, enhanceSuccessAction and/or enhanceFailAction
to mutate actions and request options
See rrh-auth
& rrh-alerts
repositories for implementation examples.