This a Redux utilities library for alternate approaches to creating all the redux boilerplate. It uses ramda under the hood for most of the utilities.
yarn add redux-utility
# or
npm install redux-utility
Since exports were reorganized, all that is needed is to update exports. Change this
import { Reducers } from 'redux-utility'
const { createReducer } = Reducers;
to
import { createReducer } from 'redux-utility'
The functions in the library are divided into 6 categories:
- Actions
- Hooks
- Reducers
- Redux
- Observable
- Modules
Every function inside each module is exported as a named export. A plan for rxjs like imports is on the way
The Actions object contains functions for creating action creators.
import {
nullaryAction,
unaryActionCreator,
nAryActionCreator,
shape
} from 'redux-utility'
Creates an action creator with no arguments, given the type
const inc = nullaryActionCreator("INC")
inc() // returns { type: "INC" }
Creates an action creator that receives a single argument, given the type
const withPayload = unaryActionCreator("WITH_PAYLOAD");
withPayload(42) // returns { type: "WITH_PAYLOAD" , payload: 42 }
Creates an action creator given the type and a payload constructor. Payload is constructed by calling the payload function with the given arguments
const withTwoArgs = nAryActionCreator("ADD",(a,b) => ({ a,b }))
withTwoArgs(20,22) // returns { type: "ADD" , payload: { a: 20, b: 22 } }
Shape is a simple utility added for the most common case shape receives the names of the object keys and returns a function that constructs objects based on argument position
const ABShape = shape('a','b')
ABShape(1,2) // returns { a: 1, b: 2 }
const withAB = nAryActionCreator("ADD", ABShape)
withAB(20,22) // returns { type: "ADD" , payload: { a: 20, b: 22 } }
import {
usePathSelector
} from 'redux-utility'
The hook receives a dot separated path and a default value and returns a piece of state
// state = { a : { b: { c: 42 } } }
usePathSelector("a.b.c") // returns 42
usePathSelector("a.b.d") // returns undefined
usePathSelector("a.b.d",50) // returns 50
Contains various functions that create reducers through different styles
import {
createReducer,
createPairsReducer,
createEventReducer
} from 'redux-utility'
Receives a configuration object and creates a reducer based on the object. Uses the keys as action types and calls the value associated with the key. Also a special 'default' key is reserved for when no action is handled. The default value for the default key is the identity function
const DEC = 'DEC'
const INC = 'INC'
const reducer = createReducer({
[DEC]: (state,action) => state - 1,
[INC]: (state,action) => state + 1,
// default: (state) => state
})
const dec = nullaryActionCreator(DEC)
const inc = nullaryActionCreator(INC)
reducer(5,dec()) // returns 4
reducer(5,inc()) // returns 6
reducer(5, {type: "anything"}) // returns 5
Creates a reducer based on an array of (actionType, function) pairs. Converts the array to an object and calls createReducer. Keep in mind that the array is not validated and must conform to a valid object.
const DEC = 'DEC'
const INC = 'INC'
const reducer = createPairsReducer([
[DEC, (state,action) => state - 1],
[INC, (state,action) => state + 1],
// ["default", (state) => state]
])
const dec = nullaryActionCreator(DEC)
const inc = nullaryActionCreator(INC)
reducer(5,dec()) // returns 4
reducer(5,inc()) // returns 6
reducer(5, {type: "anything"}) // returns 5
Creates a reducer based on a function that receives an object with a similar interface of that of NodeJS Event Emitters, where events are action types. Has a default event for handling the default case.
const DEC = 'DEC'
const INC = 'INC'
const reducer = createEventReducer(reducer =>
reducer.on(DEC, (state,action) => state - 1)
reducer.on(INC, (state,action) => state + 1)
// reducer.on("default", (state) => state)
])
const dec = nullaryActionCreator(DEC)
const inc = nullaryActionCreator(INC)
reducer(5,dec()) // returns 4
reducer(5,inc()) // returns 6
reducer(5, {type: "anything"}) // returns 5
Contains general redux utilities
import {
getDevtoolsCompose
} from 'redux-utility'
Returns the redux devtools compose if it exist. Otherwise returns redux compose. The function may receive an argument that when false, forces the function to return redux's compose. This could be a function or a boolean value. Common usage:
const shouldUseDevtool = () => process.env.NODE_ENV === "development"
const composeEnhancers = getDevtoolsCompose(shouldUseDevtool)
const store = createStore(
reducer,
initialState,
composeEnhancers(...enhancers)
)
Contains utilities to use with redux-observable
import {
fromActions,
fromActionsEager
} from 'redux-utility'
Creates an observable creator function from the given action creators. The observable created emits the actions in the given order by passing the given arguments to the action creators. Useful when a piece of state is needed or the actions have a payload. When the emitted actions have no needed payload, it is commonly better to use the eager version of this utility.
const DEC = 'DEC'
const INC = 'INC'
const reducer = createReducer({
[DEC]: (state,action) => state - action.payload,
[INC]: (state,action) => state + action.payload,
})
const inc = unaryActionCreator("INC")
const dec = unaryActionCreator("DEC")
const obs = fromActions( inc, dec )(5)
// --- inc(5) --- dec(5) |--->
// Common usage
const epic = actions$ => actions$.pipe(
ofType("START"),
map(() => 5)
mergeMap(
fromActions(
inc,
dec
) // returns the same as fromActions( inc, dec )(5)
)
)
Eager version of fromActions. Creates an action observable from the given action creators by calling them with no arguments. Similar to calling rxjs Observable.from with an array of actions.
const DEC = 'DEC'
const INC = 'INC'
const reducer = createReducer({
[DEC]: (state,action) => state - 1,
[INC]: (state,action) => state + 1,
})
const inc = nullaryActionCreator("INC")
const dec = nullaryActionCreator("DEC")
const obs = fromActionsEager( inc, dec )
// --- inc() --- dec() |--->
// Common usage
const epic = actions$ => actions$.pipe(
ofType("START"),
mergeMap(() =>
fromActionsEager(
inc,
dec
) // returns the same as fromActionsEager( inc, dec )
)
)
Contains common use cases ready to be used. Currently contains:
- createAsyncState
Common pattern for async state. creates a fetch, success, and error action with their respective constants, reducer function, reducer config, reducer register function, reducer pairs config and reducer initial state. The usage is as follows:
import { createAsyncState } from 'redux-utility';
// Basic usage
// receives a key for namespacing the constants. The constants will be prefixed with the key
const reducer = createAsyncState("user");
// optionally, if you want the state to be nested inside the key provided
const nested = createAsyncState("user",{ nested: true });
export default reducer;
export const {
fetch: FETCH_USER,
success: USER_SUCCESS,
error: USER_ERROR
} = reducer.constants;
export const {
fetch: fetchUser,
success: userSuccess,
error: userError
} = reducer.actions;
// more options added for flexibility
createReducer(reducer.config);
createPairsReducer(reducer.pairs);
createEventReducer(reducer.register);
// mixing with other pieces of state
const OTHER = "OTHER";
createReducer({
...reducer.config,
[OTHER]: (state) => ({ ...state, other: true }),
})
createPairsReducer([
...reducer.pairs,
[OTHER, (state) => ({ ...state, other: true })]
])
createEventReducer((handler) => {
reducer.register(handler);
handler.on(OTHER,(state) => ({ ...state, other: true }))
})
// extension of reducer
const async = createAsyncState("auth")
const extendedReducer = async.extend({
success: (state,action) => {
return {
...state,
authenticated: true
}
}
})
// or the long way around
const extended = createReducer({
[aync.constants.success]: async.extend.success((state,action) => {
return {
...state,
authenticated: true
}
}),
})
// this are the possible states
const initialState = reducer.initialState
// {
// loading: false,
// data: undefined,
// error: undefined
// }
dispatch(fetchUser())
// {
// loading: true,
// data: undefined,
// error: undefined
// }
dispatch(userSuccess({ name: "User" }))
// {
// loading: false,
// data: { name: "user" },
// error: undefined
// }
dispatch(userError("Something went wrong"))
// {
// loading: false,
// data: undefined,
// error: "Something went wrong"
// }