@duckness/duck
v1.0.0
Published
@duckness/duck - Modular Redux Ducks hatchery
Downloads
311
Readme
@duckness/duck
Modular Redux Ducks hatchery.
Example
// counterDuck.js
import Duck from '@duckness/duck'
// Create duck with the name 'counter' for 'counter-app'
const counterDuck = Duck('counter', 'counter-app')
// Export action creators
counterDuck.action('incrementCounter', 'INCREMENT')
// counterDuck.action.incrementCounter will build actions with type 'counter-app/counter/INCREMENT'
counterDuck.action('decrementCounter', 'DECREMENT')
// counterDuck.action.decrementCounter will build actions with type 'counter-app/counter/DECREMENT'
// Add selector
counterDuck.selector('counter', state => (state.counter || 0))
// Add reducers
counterDuck.reducer('INCREMENT', (state, _action, duckFace) => {
// duckness adds duckFace to every reducer
// duckFace is an interface to duck with access to selectors, action types and root reducer
return {
...state,
counter: duckFace.select.counter(state) + 1
}
})
counterDuck.reducer('DECREMENT', (state, _action, duckFace) => {
return {
...state,
counter: duckFace.select.counter(state) - 1
}
})
// Duck itself is a root reducer
export default counterDuck
Table of Contents
- Example
- API
- @Duckness packages:
API
Create Duck
Create a new duck with duckName and poolName (poolName is a namespace for ducks)
import Duck from '@duckness/duck'
const myDuck = Duck('duck-name', 'pool-name')
.duckName
const myDuck = Duck('duck-name', 'pool-name')
myDuck.duckName
// => 'duck-name'
.poolName
const myDuck = Duck('duck-name', 'pool-name')
myDuck.poolName
// => 'pool-name'
Actions
.mapActionType(actionType)
Maps short action type to long action type
myDuck.mapActionType('ACTION_TYPE')
// => 'pool-name/duck-name/ACTION_TYPE'
.action(actionName, actionType, payloadBuilder?, actionTransformer?)
Build action creator and register it under actionName (if actionName present)
const eatFish = myDuck.action('eatAllTheFish', 'EAT_FISH')
eatFish({ amount: 10 })
// => { type: 'pool-name/duck-name/EAT_FISH', payload: { amount: 10 } }
myDuck.action.eatAllTheFish({ amount: 9000 })
// => { type: 'pool-name/duck-name/EAT_FISH', payload: { amount: 9000 } }
Optional payloadBuilder
could be specified to customize payloads
const eatFish = myDuck.action(null, 'EAT_FISH', payload => {
return { amount: payload }
})
eatFish(10)
// => { type: 'pool-name/duck-name/EAT_FISH', payload: { amount: 10 } }
Optional actionTransformer
could be specified to customize action
const eatFish = myDuck.action(null, 'EAT_FISH', null, action => {
return { ...action, wellFed: true }
})
eatFish({ amount: 10 })
// => { type: 'pool-name/duck-name/EAT_FISH', payload: { amount: 10 }, wellFed: true }
If Error
object is passed to action creator payloadBuilder
will be skipped and action.error
will be true
const eatFish = myDuck.action(null, 'EAT_FISH')
eatFish(new Error('no more fish'))
// => { type: 'pool-name/duck-name/EAT_FISH', payload: Error('no more fish'), error: true }
.action[]
Calls registered action creator by its name
const eatFish = myDuck.action('eatAllTheFish', 'EAT_FISH')
myDuck.action.eatAllTheFish({ amount: 9000 })
// => { type: 'pool-name/duck-name/EAT_FISH', payload: { amount: 9000 } }
.listActionTypes()
Returns all known short action types. Type is known if .mapActionType
or .action
was called with it.
myDuck.listActionTypes()
// => ['EAT_FISH', 'QUACK']
.actionTypes[]
Is an object that maps known short action types to long action types
myDuck.actionTypes.EAT_FISH
// => 'pool-name/duck-name/EAT_FISH'
Selectors
.selector(selectorName, selector)
Registers selector under selectorName
myDuck.selector('counter', (state, _duckFace) => (state.counter || 0))
.select[]
Calls registered selector
const state = { counter: 10 }
myDuck.select.counter(state)
// => 10
Reducers
.reducer(actionType, reducer)
Registers reducer for specific action type
myDuck.reducer('EAT_FISH', (state, action, _duckFace) => {
return {
...state,
fishEaten: state.fishEaten + action.payload.amount
}
})
.reducer(null, reducer)
Registers wildcard reducer for all duck action types (ones that starts with 'pool-name/duck-name/')
myDuck.reducer(null, (state, action, _duckFace) => {
return {
...state,
updated: (state.updated || 0) + 1
}
})
.reducer(withActions(action, duckFace), reducer)
Registers reducer with action filter
myDuck.reducer((action, _duckFace) => action.error, (state, action, _duckFace) => {
return {
...state,
errors: (state.errors || 0) + 1
}
})
Root reducer
Duck itself is a root reducer for all registered reducers
myDuck( state, duckAction(payload) )
// => reduced state
duckFace
duckFace is an interface to duck that is added as a last argument to each registered selectors and reducers
myDuck.selector('counter', (some, selector, args, duckFace) => {
// ...
}
myDuck.reducer('EAT_FISH', (state, action, duckFace) => {
// ...
}
duckFace.actionTypes[]
Is an object that maps known short action types to long action types
duckFace.actionTypes.EAT_FISH
// => 'pool-name/duck-name/EAT_FISH'
duckFace.action[]
Calls registered action creator by its name
dispatch(duckFace.action.eatFish())
duckFace.mapActionType(actionType)
Maps short action type to long action type
duckFace.mapActionType('ACTION_TYPE')
// => 'pool-name/duck-name/ACTION_TYPE'
duckFace.listActionTypes()
Returns all known short action types. Type is known if .mapActionType
or .action
was called with it.
duckFace.listActionTypes()
// => ['EAT_FISH', 'QUACK']
duckFace.select[]
Calls registered selector
const state = { counter: 10 }
duckFace.select.counter(state)
// => 10
duckFace.reduce(state, action)
Calls duck root reducer
const prepareFish = myDuck.action(null, 'PREPARE_FISH')
myDuck.reducer('EAT_FISH', (state, action, duckFace) => {
const preparedState = duckFace.reduce(state, prepareFish())
return {
...preparedState,
// ...
}
})
duck.duckFace
duckFace can also be accessed from the duck itself by duck.duckFace
name.
Context
Create Duck with context
Duck can be created with duckContext
that will be accessible for selectors and reducers through duckFace
const themeDuck = Duck('theme', 'my-app', { highlightColor: 'blue' })
themeDuck.duckFace.duckContext.highlightColor
// => 'blue'
duck.updateContext
Duck context can be replaced with duck.updateContext(newContext)
themeDuck.updateContext({ highlightColor: 'red' })
Clone duck
Duck can be cloned by calling .clone(duckName, moduleName, duckContext)
.
Cloned duck will contain selectors, reducers and known action types copied from original duck with
all action types adjusted to duckName
and moduleName
.
duckContext
will be replaced.
Cloned duck can be expanded further.
const baseDuck = Duck('base', 'my-app')
baseDuck.reducer('BASE_ACTION', /* ... */)
const extendedDuck = baseDuck.clone('extended', 'my-app')
extendedDuck.reducer('ANOTHER_ACTION', /* ... */)
@Duckness packages:
- @duckness/duck - Modular Redux Ducks hatchery
- @duckness/saga - Redux Saga extension for @duckness/duck
- @duckness/epic - Redux-Observable extension for @duckness/duck
- @duckness/pool - @duckness/duck + Redux
- @duckness/pool-saga-stream - @duckness/saga plugin for @duckness/pool
- @duckness/pool-epic-stream - @duckness/epic plugin for @duckness/pool
- @duckness/react-redux-pool - @duckness/pool + React-Redux
- @duckness/use-redux - React hook for Redux store
- @duckness/use-pool - React hook for @duckness/pool.
- @duckness/store - simple store for React components
- @duckness/reactor - reactive data flow builder