@customd/cd-redux-helpers

0.4.0-b.3 • Public • Published

Redux Helpers

This library aims to provide a set of simple, easy to use factory methods to help reduce the boilerplate of writing Redux reducers and action creators.

Go to live examples, code and docs!

pipeline status coverage report


Getting Started

Install with Yarn

yarn add @customd/cd-redux-helpers

Or with NPM

npm install --save @customd/cd-redux-helpers

Reducers

Implement a full reducer managing byId, ids, loading and error state keys:

import { createReducers } from '@customd/cd-redux-helpers'

export default createReducers('articles', ['byId', 'ids', 'loading', 'error'])

export const getArticles = (state) => Object.values(state.byId)
export const getArticleIds = (state) => state.ids
export const getArticlesById = (state) => state.byId
export const getArticleById = (state, id) => state.byId[id]
export const getArticleLoading = (state) => state.loading
export const getArticleError = (state) => state.error

Available reducers are as follows:

Reducer Name Description
byId  An object of objects, arranged against id key.
ids An array of IDs which maintains order of items as passed in the action
loading  Boolean which tells you whether it's loading or not
saving  Boolean which tells you whether it's saving or not
error An error key, typically set to the thrown error object

Pick-n-mix implementation

This example implements a fully featured reducer set which is functionally identical to the createReducer example above, except adds an additional custom reducer.

import { combineReducers } from 'redux'
import {
	createReducerById,
	createReducerIds,
	createReducerLoading,
	createReducerError
} from '@customd/cd-redux-helpers/dist/reducers'

const byId    = createReducerById('articles')
const ids     = createReducerIds('articles')
const loading = createReducerLoading('articles')
const error   = createReducerError('articles')

const customReducer = (state = false, action) => {
	switch(action.type) {
		case 'CUSTOM_ARTICLES_ACTION':
			return true
		default:
			return state
	}
}

export default combineReducers({
	byId,
	ids,
	loading,
	error,
	customReducer
})

export const getArticles = (state) => Object.values(state.byId)
export const getArticleIds = (state) => state.ids
export const getArticlesById = (state) => state.byId
export const getArticleById = (state, id) => state.byId[id]
export const getArticleLoading = (state) => state.loading
export const getArticleError = (state) => state.error

Composition

These methods are composable. Suppose you want to handle an additional action CLEAR_ARTICLES_IDS in the ids reducer.

import { createReducerIds } from '@customd/cd-redux-helpers/dist/reducers'

const reduceIds = createReducerIds('articles')

const ids = (state = [], action) => {
	if( action.type === 'CLEAR_ARTICLES_IDS') {
		return []
	}
	return reduceIds(state, action)
}

export default ids

Action Creators

You can also use this package to provide re-usable and composable action creators, including async / redux-thunk action creators.

Basic action creators

Available 'basic' action creator factory methods — these factories will create simple action creators which return actions suitable for use with the reducers generated by this package.

Factory Method Type Description
createSetAction basic A SET_X action creator
createAddAction basic An ADD_X action creator
createUpdateAction basic An UPDATE_X action creator
createLoadingAction basic A SET_X_LOADING action creator
createSavingAction basic A SET_X_SAVING action creator
createErrorAction basic A SET_X_ERROR action creator

Each of the below factory methods accepts a single argument which serves as the action name.

E.g.,

const setArticles = createSetAction('articles')

setArticles([{ id: 1, title: 'foo' }])

And implement a full set of actions to work with the above reducers:

import {
	createSetAction,
	createAddAction,
	createUpdateAction,
	createLoadingAction,
	createSavingAction,
	createErrorAction
} from '@customd/cd-redux-helpers/dist/actions'

export const setArticles = createSetAction('articles')
export const addArticles = createAddAction('articles')
export const updateArticles = createUpdateAction('articles')
export const setArticlesLoading = createLoadingAction('articles')
export const setArticlesSaving = createSavingAction('articles')
export const setArticlesError = createErrorAction('articles')

Limit action update scope

Let's say you want an action which sets a single blog article for use elsewhere in the site, and an action which bulk-sets articles to display in a list. You don't want your first article to appear in the list, as it may not be relevant, however because you're DRY and you want to take best advantage of Redux by opportunistically re-using your pre-loaded articles, you decide you want all articles stored in the same state.blog.articles.byId state.

You can use @customd/cd-redux-helpers scope limiting updaters, to change the action you fire so it only applies to the byId state, and leaves the ids state alone (or vice versa).

import {
	createUpdateAction,
	SET_BY_ID_ONLY
} from '@customd/cd-redux-helpers/dist/actions'

export const updateArticlesDetail = createUpdateAction('articles', SET_BY_ID_ONLY)

This has the effect of changing the action you're firing from UPDATE_ARTICLES to UPDATE_ARTICLES_BY_ID. This action works exactly the same on the byId state, but is ignored by the ids state entirely.

Async action creators

There are also factory methods for async action creators (intended for use with redux-thunk middleware).

createFetchAction

This method makes an async action creator which is aimed at returning an ordered set of IDs matching a request. Ideally for large data sets this would return only an array of IDs, though for smaller data-sets, you may also like to return a flag field, or a small amount of content for client side filtering, and for very small data sets you may simply return all the data you need in this single request.

import Blog from 'api/Blog'
import {
	createFetchAction,
	createUpdateAction,
} from '@customd/cd-redux-helpers/dist/actions'


export const updateArticles = createUpdateAction('articles')

export const fetchArticles = createFetchAction(
	Blog.getWhere,
	updateArticles
)

Often you'll want to provide a default set of arguments to the API query:

import Blog from 'api/Blog'
import {
	createFetchAction,
	createUpdateAction,
} from '@customd/cd-redux-helpers/dist/actions'


export const updateArticles = createUpdateAction('articles')

export const fetchArticles = createFetchAction(
	[Blog.getWhere, { fields: 'id', status: 'published' }, '-published'],
	updateArticles
)

A more advanced implementation tracking loading & errors:

import Blog from 'api/Blog'
import {
	createFetchAction,
	createUpdateAction,
	createLoadingAction,
	createErrorAction
} from '@customd/cd-redux-helpers/dist/actions'


export const updateArticles = createUpdateAction('articles')
export const setArticlesLoading = createLoadingAction('articles')
export const setArticlesError = createErrorAction('articles')

export const fetchArticles = createFetchAction(
	[Blog.getWhere, { fields: 'id', status: 'published' }, '-published'],
	updateArticles,
	{
		loadingAction : setArticlesLoading,
		errorAction   : setArticlesError
	}
)
Action creator usage

Somewhere in your connected component, make the call. Note that you can override the default Blog.getWhere arguments when calling.

const { fetchArticles, authorId } = this.props

fetchArticles({ fields: 'id,title,published' })
Arguments
Position Type Description
1 Array or Callable A Callable which returns a Promise, or an array, where the remaining array items are passed to the Callable as a default set of arguments. Promise should return an object with minimum { result: 'success', data: '...' }, or { result: 'error', error: '...' }
2 Callable A Callable which returns an action creator (can be either a thunk action, or a vanilla action). This Callable recieves the response.data key (or whatever response key as defined in the responseData option).
3 Object A configuration object, with options as described below.
Options
Option Default Description
loadingAction - The action to fire when loading starts
errorAction - The action to fire if we encounter an arror in the fetch request
responseResult result The property name of the response to use when checking for a response result success
responseData data The property name for the data to pass to the update action from the response object. Pass ACTION_RETURN_FULL_RESPONSE constant to return the full response object.
responseError error The property name for the error content used to throw Error when responseResult key is not === 'success'. Pass ACTION_RETURN_FULL_RESPONSE constant to throw the full response instead of an Error.

createFetchDetailAction

This method makes an async action creator which is aimed at fetching record detail from an API by record ID. The purpose is to feed in an array of IDs which will be retrieved.

import Blog from 'api/Blog'
import {
	createFetchDetailAction,
	createUpdateAction,
} from '@customd/cd-redux-helpers/dist/actions'


export const updateArticles = createUpdateAction('articles')

export const fetchArticlesDetail = createFetchDetailAction(
	[Blog.getWith, { status: 'published' }, ['authors', 'tags']],
	updateArticles
)
Action creator usage

Somewhere in your connected component, make the call. Note that you can override the default Blog.getWith arguments when calling.

const { fetchArticlesDetail, authorId } = this.props

fetchArticlesDetail({ authorId }, ['authors', 'tags', 'categories', 'comments'])
Arguments
Position Type Description
1 Array or Callable A Callable which returns a Promise, or an array, where the remaining array items are passed to the Callable as a default set of arguments. Promise should return an object with minimum { result: 'success', data: '...' }, or { result: 'error', error: '...' }
2 Callable A Callable which returns an action creator (can be either a thunk action, or a vanilla action). This Callable recieves the response.data key (or whatever response key as defined in the responseData option).
3 Object A configuration object, with options as described below.
Options
Option Default Description
loadingAction - The action to fire when loading starts
errorAction - The action to fire if we encounter an arror in the fetch request
responseResult 'result' The property name of the response to use when checking for a response result success
responseData 'data' The property name for the data to pass to the update action from the response object. Pass ACTION_RETURN_FULL_RESPONSE constant to return the full response object.
responseError 'error' The property name for the error content used to throw Error when responseResult key is not === 'success'. Pass ACTION_RETURN_FULL_RESPONSE constant to throw the full response instead of an Error.
chunkSize 500 Chunks request IDs up into groups of no larger than this value. Pass ACTION_DONT_CHUNK constant to stop this method from chunking requests.

Custom response manipulation

Let's say you need to adjust some content before you update the articles. Since we're just passing plain methods around, you can compose the updateArticles function call;

import {
	createUpdateAction,
	createSetAction,
	createFetchAction,
	ACTION_RETURN_FULL_RESPONSE
} from '@customd/cd-redux-helpers/dist/actions'

export const updateArticles = createUpdateAction('articles')
export const setArticleCount = createSetAction('articles_count')

export const fetchArticles = createFetchAction([ Blog.getWhere,
		{ status: 'published' },
		'-published'
	],

	(response) => {

		const nextArticles = response.articles.map(({ id, title, abstract }) =>({
			id,
			title: title.trim()
			abstract: abstract.trim()
		}))

		return (dispatch, getState) => {

			dispatch(updateArticles(nextArticles))
			dispatch(setArticleCount(response.pagination.count))
		}
	},

	{
		loadingAction: setArticlesLoading,
		errorAction: setArticlesError,
		responseData: ACTION_RETURN_FULL_RESPONSE
	}
)

The same concept applies to Loading and Error actions.

Contributing

If you find a bug, error or feature that you wish to fix/implement and contribute back to the project, you can do so by committing your work to a new branch, and issuing a merge request.

Once you've created your branch, push it to the repository, and then issue a merge request targeting develop or a release branch.

Make sure you leave a note about why this fix is important, how you found the solution, and any implications this solution might have. Use the merge request template provided.

GitLab will automatically send out an email to the maintainer — that person will then be able to review, test and ensure the change is documented.

The person assigned to the merge will:

  • Test the merge request against the develop branch.
  • Document the modifications.
  • Publish a new release if required, or add to the next release.
  • Communicate with the developer who raised the request, and work out if the change needs to be implemented as a hotfix update for earlier major versions.
  • Ensure tests have been written, and pass.
  • Ensure new features or behaviours have been documented.

Publishing

Ensure you're logged in and registered to npm.customd.com before publishing.

  1. Commit and push all changes to a release/xyz branch
  2. Run yarn publish
  3. Merge and squash changes in to master

Readme

Keywords

none

Package Sidebar

Install

npm i @customd/cd-redux-helpers

Weekly Downloads

0

Version

0.4.0-b.3

License

MIT

Unpacked Size

64.2 kB

Total Files

9

Last publish

Collaborators

  • custom-d