0.3.4 • Published 4 years ago

@gggdomi/rrh v0.3.4

Weekly downloads
12
License
MIT
Repository
github
Last release
4 years ago

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: 1. By creating a saga that will take RRH's special actions 2. By implementing beforeRequest, enhanceStartAction, enhanceSuccessAction and/or enhanceFailAction to mutate actions and request options

See rrh-auth & rrh-alerts repositories for implementation examples.