node package manager

redux-crud-async

redux-crud-async

Build Status

redux-crud-async will create CRUD async actions and states automatically, just for you. The configuration is minimalist.

It currently uses axios or sails.io websocket (custom socket.io) for XHR. It allows you to use a REST API with authentication with a Bearer Token. In a near future, I will implement the possibility to create random actions like sign_in, sign_out or tranformThisLeadInGold Redux-crud-async is built against 150+ tests. Unfortunately this does not mean that redux-crud-async is bug free, please send issues if you find one !

Table of Contents

  1. Setup
  2. Conventions a. Routes b. Authentication c. Socket
  3. Configuration
  4. Actions a. Names b. Additionnal Actions
  5. States (Reducers)
  6. Todo
  7. Change Log

Setup

NPM

you can find it on NPM

npm i -S redux-crud-async

ActionTypes

// redux/actionTypes/index.js 
  var reduxCrudAsync = require('redux-crud-async')
  var crud = new reduxCrudAsync()
 
  module.exports = {
    ...crud.primaryActionTypesFor('channel'),
    ...crud.primaryActionTypesFor('tag'),
 
    ...crud.associationActionTypesFor('channel', 'tag')
  }
 

Actions

// redux/actions/index.js 
  var reduxCrudAsync = require('redux-crud-async')
 
  var hostConfig = {
    host            : 'https://my-api.com',
    prefix          : 'v1', // Optional - default to '' 
    pluralizeModels : false, // Optional - default to true 
 
  }
 
  var crud = new reduxCrudAsync(hostConfig) // hostConfig is mandatory for actionsGenerator 
 
  module.exports = {
    ...crud.primaryActionsFor('channel'),
    ...crud.primaryActionsFor('tag'),
 
    ...crud.associationActionsFor('channel', 'tag')
  }
 

Reducers

// redux/reducers/index.js 
  var reduxCrudAsync = require('redux-crud-async')
  var crud = new reduxCrudAsync()
 
  module.exports = {
    ...crud.primaryReducerFor('user'),
    ...crud.primaryReducerFor('pet'),
 
    ...crud.associationReducerFor('user', 'pet')
  }
 

EXEMPLE USAGE Click here to see how much it is easy to use this module


Conventions

General

It might be obvious but all models returned by your database need to have an unique id.

Routes

This module is built to work with sails.js blueprints routes using sails-rest-api conventions. It differentiate singular and plural model name : findUser !== findUsers IMPORTANT ! As sails.js, this module uses pluralize module which pluralize words grammaticaly.

some exemples : channel -> channels person -> people coach -> coaches

By default, all your routes will be pluralized, Person model will have the following :

state : person - a single person people - all your "persons"

actions : findPerson -> will hit GET /people/:id findPeople -> will hit GET /people

You can unpluralize your urls by setting it in the config

Authentication

redux-crud-async uses a Bearer Token to authenticate requests. It is store in window.localStorage. Every request which need authentication is sent with the token in the header following this convention :

Token is set in Authorization header as :

// Config sent to axios or socket.io 
{
    headers : {
      Authorization : 'Bearer '+ JWT_Token_from_localStorage
    }
}

Socket

We use the io.socket.request to communicate to the server. Make sure that your server can use it if you don't use sails.js on server side.


Configuration

Config file

Name Type Default Description
host String null Your API host - must be defined
prefix String null A prefix for all your routes. Don't add the slash / on the prefix it will be automatically added.
pluralizeModels Boolean true Use pluralized model names in url. This has no affect on action names.
socket Boolean false Use socket.io for actions
localStorageName String "JWT" The key for retrieving your JWT Token from window.localStorage
headerContent String "Bearer {{JWT}}" The format of your header content The format is affected by localStorageName
headerFormat String "Authorization" The format of your header authorization key
apiSpecs Object null Routes where you want to use the JWT_Token
responseSchemas Object {http: {success : 'data', error : 'data'}, socket : {success : null, error : null}} schemas of your API's responses. Where the data can be found in your response object

The format of headerContent is affected by localStorageName if you change the default value

{
  localStorageName : 'myJWT',
  headerContent : 'MyContent {{myJWT}}'
}

For apiSpecs you just have to set the unpluralized modelName or primarymodelAssociatedmodels with an auth property inside which contains an array of actions to authenticate. Just follow conventions given above.

{
  host            : 'http://your-api-host',
  prefix          : 'my-prefix',
  pluralizeModels : false,
  socket          : true,
  localStorageName: 'MyJWT',
  headerContent   : 'AuthBearer {{MyJWT}}',
  headerFormat    : 'AuthBearerHeaderKey'
  apiSpecs : {
 
    coach : {
      // All the following actions will be beared with a JWT 
      auth : ['findCoaches', 'createCoach', 'updateCoach']
    },
 
    coachComments : {
      auth : ['addCommentToCoach', 'removeCommentFromCoach']
    }
  }
}

Results

findPerson -> will hit GET http://your-api-host/my-prefix/people/:id findPeople -> will hit GET http://your-api-host/my-prefix/people

With pluralizeModels : false findPerson -> will hit GET http://your-api-host/my-prefix/person/:id findPeople -> will hit GET http://your-api-host/my-prefix/person


Actions

Names

There is a maximum of 11 actions for a given model which will be automatically understood by reducers. 3 status actions are dispatched for every redux-crud-async action : START, SUCCESS and ERROR. They are automatically understood by reducers. eg. primary model = channel, associated model = tag

3 primary

actionName url param state state Type
findChannel GET channels/:id String channel Object
findChannels GET channels?request String channels [Object]
createChannel POST channels Object OR FormData channel Object
updateChannel PUT channels Object channel Object
destroyChannel DELETE channels Object channel Object

You can submit a FormData to create but the FormData is transmitted as is and model will not be appended to the state. If you need the model to be appended to the state, use a javascript object instead of a FormData. FormData can be used to send specific CRUD actions like uploading an image

3 association

actionName url parameters state state Type Comment
findChannelTags GET channels/:channelId/tags/:tagId? String, String channelTags [Object]
addTagToChannel POST channels/:channelId/tags/:tagId? String, String OR Object channelTag Object if no tag id is set you must give an object to this function
createChannel POST channels String, String channelTag Object

Additionnal actions

An additionnal action exists which empties reducers. Dispatch manually this action to empty your reducers.

{
  type : 'EMPTY_CHANNEL'
}
{
  type : 'EMPTY_CHANNELS'
}
{
  type : 'EMPTY_PRIMARY_ASSOCIATED_MODELS'
}

Have a look in primaryActionGenerator and associationActionGenerator and Actions Details for more precision about dispatched actions.

States (Reducers)

Reducers return the following states usable in your components

state type
channel Object
isFindingChannel Boolean
channels [Object]
isFindingChannels Boolean
channelTags [Object]
isFindingChannelTags Boolean

See reducers :


TODO

  • make API endpoints editables
  • add cache for get requests
  • cache timeout by route
  • remove arrow functions in tests
  • comment code
  • find a way to test FormData in createModel
  • add single actions (signup, signin)
  • add coverage
  • move associated record uuid generation from associationActions dispatch to reducer
  • state immutability
  • add tests for caching
  • add some headers tests

Change Log

0.7.0
  • removed second parameter from update action
0.6.3
  • update and delete primary actions
  • fix bug when building with webpack - io was undefined
  • fix silly bugs and add tests for them
0.5.0
  • API expectation editables
0.4.0
  • add socket.io support through the window.io variable
  • more tests
  • rewrite of the utils/xhr/* module
  • doc changes
  • added EMPTY_CHANNELS action in reducers