redux-smart-creators
v1.5.2
Published
Smart creators for actions and reducers
Downloads
14
Maintainers
Readme
Documentation
Examples
WIP
Getting Started
Usage with Typescript
Getting Started
Installation
# with npm
npm install redux-smart-creators
# with yarn
yarn add redux-smart-creators
Action creators
There are two types of action creators:
- Basic action creators return an object with an action type, and a payload (if a payload is specified)
- Asynchronous action creators return an object with multiple basic action creators, each of which is characteristically responsible for a specific step of the asynchronous action.
Basic action creators
To create a basic action, use the getCreator
function
import { getCreator } from 'redux-smart-creators'
Basic action creators without payload
Let's imagine that we have storage for a basic counter: { count: 0 }
.
Let's make an action creator to increment the counter by 1.
This creator does not require a payload and has a 'incrementByOne' type.
const incrementByOne = getCreator('incrementByOne');
/** equal to:
* const incrementByOne = () => ({
* type: 'incrementByOne'
* })
*/
store.dispatch(incrementByOne()) // dispatched { type: 'incrementByOne' }
Basic action creators with payload
In some cases, we need to increment the counter by a given value.
To do this, we need an action creator that will accept the payload.
To create it, use the load
method
const incrementByValue = getCreator('incrementByValue').load()
/** equal to:
* const incrementByValue = (value) => ({
* type: 'incrementByValue',
* payload: value
* })
*/
store.dispatch(incrementByValue(10)) // dispatched { type: 'incrementByValue', payload: 10 }
Using more complex payloads
Now we need to increase the counter by the specified number several times.
To do this, we use the load
method along with a function that will calculate the value to add.
const calculateValue = (value, times) => value * times;
const incrementManyTimes = getCreator('incrementManyTimes').load(calculateValue)
/** equal to:
* const incrementManyTimes = (value, times) => ({
* type: 'incrementManyTimes',
* payload: value * times
* })
*/
store.dispatch(incrementManyTimes(10, 2)) // dispatched { type: 'incrementManyTimes', payload: 20 }
// You also can use inline functions:
const decrementManyTimes = getCreator('decrementManyTimes').load((value, times) => value * times)
Asynchronous action creators
Asynchronous action creator is an object with multiple basic action creators, each of which is characteristically responsible for a specific step of the asynchronous action. It also has a 'load' method for getting payload for chosen steps.
import { getAsyncCreator } from 'redux-smart-creators'
Asynchronous actions without Payload
For example, we have some storage: { count: 0 }
.
Now we are increasing the counter using a request to the server,
and we need to track the various states of this action, such as initiation
, loading
, success
and error
.
To get these actions in one package, use the getAsyncCreator
function.
By default, it returns an object with four steps (INIT
, LOADING
, SUCCESS
, FAILURE
), represented as basic action creators without a payload.
All of them will have computed action type, consisting of the type specified in the getAsyncCreator
and the step name.
Let's create an asynchronous increase by a given value:
const incrementByOneAsync = getAsyncCreator('incrementByOneAsync');
store.dispatch(incrementByOneAsync.INIT()) // dispatched { type: 'incrementByOneAsync[INIT]' }
store.dispatch(incrementByOneAsync.LOADING()) // dispatched { type: 'incrementByOneAsync[LOADING]' }
store.dispatch(incrementByOneAsync.SUCCESS()) // dispatched { type: 'incrementByOneAsync[SUCCESS]' }
store.dispatch(incrementByOneAsync.FAILURE()) // dispatched { type: 'incrementByOneAsync[FAILURE]' }
Defining own steps
If you want to define own steps (instead of INIT
, LOADING
, SUCCESS
, FAILURE
)
for asynchronous action creators, provide them as array in second argument:
const Async = incrementByOneAsync('incrementByOne', ['start', 'finish', 'error']);
store.dispatch(incrementByOneAsync.start()) // dispatched { type: 'incrementByOne[start]' }
store.dispatch(incrementByOneAsync.finish()) // dispatched { type: 'incrementByOne[finish]' }
store.dispatch(incrementByOneAsync.error()) // dispatched { type: 'incrementByOne[error]' }
Asynchronous actions creators with payload
For providing a payload to asynchronous action creator use load
method and payload
helper function.
You need to provide an object with steps you want to be injected with a payload.
Use the payload
function at the desired step to upgrade a step's action creator:
import { payload } from 'redux-smart-creators'
const incrementByValueAsync = getAsyncCreator('incrementByValue').load({
INIT: payload(),
SUCCESS: payload(),
FAILURE: payload()
})
store.dispatch(incrementByValueAsync.INIT(10)) // dispatched { type: 'incrementByValue[INIT]', payload: 10 }
store.dispatch(incrementByValueAsync.SUCCESS(true)) // dispatched { type: 'incrementByValue[SUCCESS]', payload: true }
store.dispatch(incrementByValueAsync.FAILURE('Server Error')) // dispatched { type: 'incrementByValue[FAILURE]', payload: 'Server Error' }
// The rest of the steps are left unchanged:
store.dispatch(incrementByValueAsync.LOADING()) // dispatched { type: 'incrementByValue[LOADING]' }
Providing payload functions
To create more complex payloads, use your functions instead of imported payload
function, as in basic actions creators
import { payload } from 'redux-smart-creators'
const calculateValue = (value, times) => value * times;
const incrementManyTimesAsync = getAsyncCreator('incrementManyTimes').load({
INIT: payloadFunction, // You can use declared function
SUCCESS: payload,
FAILURE: (error) => error.message, // or inline functions
})
store.dispatch(incrementManyTimesAsync.INIT(10, 2)) // dispatched { type: 'incrementManyTimes[INIT]', payload: 20 }
store.dispatch(incrementManyTimesAsync.SUCCESS(true)) // dispatched { type: 'incrementManyTimes[SUCCESS]', payload: true }
store.dispatch(incrementManyTimesAsync.FAILURE({ message: 'Server Error', status: 500 })) // dispatched { type: 'incrementManyTimes[FAILURE]', payload: 'Server Error' }
// The rest of the steps are left unchanged:
store.dispatch(incrementManyTimesAsync.LOADING()) // dispatched { type: 'incrementManyTimes[LOADING]' }
Retrieving the action type
Actions creators have a type
property that contains the literal of the corresponding action.
Use this property when you need to access the action type.
const incrementByOne = getCreator('incrementByOne');
console.log(incrementByOne.type) // incrementByOne
const asyncIncrement = getAsyncCretor('asyncIncrement');
console.log(incrementByOne.INIT.type) // incrementByOne[INIT]
console.log(incrementByOne.LOADING.type) // incrementByOne[LOADING]
// with own steps:
const asyncDecrement = getAsyncCretor('asyncDecrement', ['start', 'finish']);
console.log(incrementByOne.start.type) // incrementByOne[start]
console.log(incrementByOne.finish.type) // incrementByOne[finish]
Creators pack
It is often necessary to combine several actions into one pack with same label.
To create a package use the getCreatorPack
function.
The received package will have the functions for getting action creators.
import { getCreatorsPack } from 'redux-smart-creators'
Pack's label
Specify the name of the package, and it will be added to the type
of each action creator obtained from this package.
const counter = getCreatorsPack('COUNTER');
const incrementByOne = counter.getCreator('incrementByOne');
console.log(incrementByOne.type) // @@COUNTER/incrementByOne
const incrementAsync = counter.getAsyncCreator('incrementAsync');
console.log(incrementAsync.INIT.type) // @@COUNTER/incrementAsync[INIT]
console.log(incrementAsync.LOADING.type) // @@COUNTER/incrementAsync[LOADING]
Pack's asynchronous steps
As the second argument, specify the steps that will be used by default in the asynchronous action creators taken from this package.
const counter = getCreatorsPack('COUNTER', ['start', 'finish']);
const incrementAsync = counter.getAsyncCreator('incrementAsync');
console.log(incrementAsync.start.type) // @@COUNTER/incrementAsync[start]
console.log(incrementAsync.finish.type) // @@COUNTER/incrementAsync[finish]
It is possible to specify steps as the only argument, but in this case, the package label will be empty and will not be applied to types of action creators.
const counter = getCreatorsPack(['start', 'finish']);
const incrementAsync = counter.getAsyncCreator('incrementAsync');
console.log(incrementAsync.start.type) // incrementAsync[start]
console.log(incrementAsync.finish.type) // incrementAsync[finish]
Package's default steps can be overwritten for a specific action creator during a getAsyncCreator
function call.
const counter = getCreatorsPack('COUNTER', ['start', 'finish']);
const incrementAsync = counter.getAsyncCreator('incrementAsync', ['init', 'success']);
console.log(incrementAsync.init.type) // @@COUNTER/incrementAsync[init]
console.log(incrementAsync.success.type) // @@COUNTER/incrementAsync[success]
Reducers
The library provides a powerful tool for declarative development of reducers. First, import the setupReducer
function
import { setupReducer } from "redux-smart-creators";
Creating basic reducer
To create a basic reducer, provide an initial state and call the create
method. This reducer will always return the initial state
const initialState = 0;
const reducer = setupReducer(initialState).create()
Reducer's action handlers
To add some logic to the reducer, use the handlers before calling the create
method.
.On
Executes the specified function while processing the specified action creator. The function takes the current state, and the payload of the action creator's action (if the action has a payload) as arguments. The result of executing the function will be a new state.
const initialState = 0;
const incrementByOne = getCreator('incrementByOne');
const incrementByValue = getCreator('incrementByValue').load()
const reducer = setupReducer(initialState)
.on(incrementByOne, (state) => state + 1)
.on(incrementByValue, (state, payload) => state + payload)
.create()
You can provide a static value instead of a function and this value will become the new state.
const initialState = 0;
const switchToOne = getCreator('switchToOne');
const reducer = setupReducer(initialState)
.on(switchToOne, 1)
.create()
To handle multiple creators that trigger the same logic, put an array with these creators as an argument.
const initialState = 0;
const increment = getCreator('increment');
const incrementAsync = getAsyncCreator('incrementAsync');
const reducer = setupReducer(initialState)
.on([increment, incrementAsync.SUCCESS], (state) => state + 1)
.create()
Usage with Typescript
Typing action creators
Action creator with payload
By default, the action creator with a payload can accept any type of argument.
To define a type, use a generic for load
method or payload
function.
import { getCreator, getAsyncCreator, payload } from 'redux-smart-creators'
const incrementByValue = getCreator('incrementByOne').load<number>();
incrementByValue('10') // Type Error;
incrementByValue(10) // Correct type;
const incrementByValueAsync = getAsyncCreator('incrementByValueAsync').load({
INIT: payload<number>(),
})
incrementByValueAsync.INIT('10') // Type Error;
incrementByValueAsync.INIT(10) // Correct type;
If you use your function to define a payload, its typing will be used in the action creator.
import { getCreator, getAsyncCreator } from 'redux-smart-creators'
const incrementManyTimes = getCreator('incrementManyTimes').load((value: number, times: number) => {
return value * times;
});
incrementManyTimes('10', 15) // Type Error;
incrementManyTimes(10, 15) // Correct type;
const roundValueAsync = getAsyncCreator('roundValueAsync').load({
INIT: Math.round,
})
roundValueAsync.INIT('10.171') // Type Error;
roundValueAsync.INIT(10.99) // Correct type;
Asynchronous action creator's steps
Steps will be automatically typed if you define them during a getAsyncCreator
or getCreatorsPack
function call.
If you define steps as a constant and then use it during a function call, the steps remain untyped.
To fix this, directly define the type for the array with steps.
import { getAsyncCreator } from 'redux-smart-creators'
// Correct usage:
getAsyncCreator('roundValueAsync', ['first', 'second']);
// Incorrect usage, steps will not be defined in the action creator's type:
const untypedSteps = ['first', 'second']
getAsyncCreator('roundValueAsync', untypedSteps);
// Correct usage:
type Steps = 'first' | 'second';
const typedSteps: Steps[] = ['first', 'second']
getAsyncCreator('roundValueAsync', typedSteps);
Inferring root action
Often you need to get the root action (a union type of multiple actions).
To get it, combine all exported action creators into one object and use it in the InfetActions
generic type.
/** counterActions.ts */
import { getCreator, getAsyncCreator, payload } from 'redux-smart-creators'
export const increment = getCreator('increment');
export const incrementByValue = getCreator('incrementByValue').load<number>();
export const asyncIncrementManyTimes = getAsyncCreator('asyncIncrementManyTimes').load({
INIT: (value: number, times: number): number => value * times,
SUCCESS: payload<boolean>(),
FAILURE: ({ message }: Error) => message
});
/** serverActions.ts */
export const checkServerStatusAsync = getAsyncCreator('checkServerStatusAsync');
/** types.ts */
import { InferActions } from 'redux-smart-creators';
import * as counterActions from './counterActions';
import * as serverActions from './serverActions';
export type CounterActions = InferActions<typeof counterActions>
/**
* | { type: 'increment' }
* | { type: 'incrementByValue', payload: number }
* | { type: 'asyncIncrementManyTimes[INIT]', payload: number }
* | { type: 'asyncIncrementManyTimes[LOADING]' }
* | { type: 'asyncIncrementManyTimes[SUCCESS]', payload: boolean }
* | { type: 'asyncIncrementManyTimes[FAILURE]', payload: string }
*/
export type ServerActions = InferActions<typeof serverActions>
/**
* | { type: 'checkServerStatusAsync[INIT]' }
* | { type: 'checkServerStatusAsync[LOADING]' }
* | { type: 'checkServerStatusAsync[SUCCESS]' }
* | { type: 'checkServerStatusAsync[FAILURE]' }
*/
export type RootAction = CounterActions | ServerActions
Typing reducers
WIP