@duckness/duck - Modular Redux Ducks hatchery
npm install @duckness/duck@duckness/duck Modular Redux Ducks hatchery.






``js
// 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
`
- Example
- API
- Create Duck
- .duckName
- .poolName
- Actions
- .mapActionType(actionType)
- .action(actionName, actionType, payloadBuilder?, actionTransformer?)
- [.action[]](#action).listActionTypes()
- .actionTypes[]
- [](#actiontypes).selector(selectorName, selector)
- Selectors
- .select[]
- [](#select).reducer(actionType, reducer)
- Reducers
- .reducer(null, reducer)
- .reducer(withActions(action, duckFace), reducer)
- duckFace.actionTypes[]
- Root reducer
- duckFace
- [](#duckfaceactiontypes)duckFace.action[]
- [](#duckfaceaction)duckFace.mapActionType(actionType)
- duckFace.listActionTypes()
- duckFace.select[]
- [](#duckfaceselect)duckFace.reduce(state, action)
- duck.duckFace
- duck.updateContext
- Context
- Create Duck with context
-
- Clone duck
- @Duckness packages:
Create a new duck with duckName and poolName (poolName is a namespace for ducks)
`js
import Duck from '@duckness/duck'
const myDuck = Duck('duck-name', 'pool-name')
`
js
const myDuck = Duck('duck-name', 'pool-name')
myDuck.duckName
// => 'duck-name'
`$3
`js
const myDuck = Duck('duck-name', 'pool-name')
myDuck.poolName
// => 'pool-name'
`Actions
$3
Maps short action type to long action type
`js
myDuck.mapActionType('ACTION_TYPE')
// => 'pool-name/duck-name/ACTION_TYPE'
`$3
Build action creator and register it under actionName (if actionName present)
`js
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
`js
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
`js
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
`js
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 }
`$3
Calls registered action creator by its name
`js
const eatFish = myDuck.action('eatAllTheFish', 'EAT_FISH')
myDuck.action.eatAllTheFish({ amount: 9000 })
// => { type: 'pool-name/duck-name/EAT_FISH', payload: { amount: 9000 } }
`$3
Returns all known short action types. Type is known if
.mapActionType or .action was called with it.`js
myDuck.listActionTypes()
// => ['EAT_FISH', 'QUACK']
`$3
Is an object that maps known short action types to long action types
`js
myDuck.actionTypes.EAT_FISH
// => 'pool-name/duck-name/EAT_FISH'
`Selectors
$3
Registers selector under selectorName
`js
myDuck.selector('counter', (state, _duckFace) => (state.counter || 0))
`$3
Calls registered selector
`js
const state = { counter: 10 }
myDuck.select.counter(state)
// => 10
`Reducers
$3
Registers reducer for specific action type
`js
myDuck.reducer('EAT_FISH', (state, action, _duckFace) => {
return {
...state,
fishEaten: state.fishEaten + action.payload.amount
}
})
`$3
Registers wildcard reducer for all duck action types (ones that starts with 'pool-name/duck-name/')
`js
myDuck.reducer(null, (state, action, _duckFace) => {
return {
...state,
updated: (state.updated || 0) + 1
}
})
`$3
Registers reducer with action filter
`js
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
`js
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
`js
myDuck.selector('counter', (some, selector, args, duckFace) => {
// ...
}
myDuck.reducer('EAT_FISH', (state, action, duckFace) => {
// ...
}
`$3
Is an object that maps known short action types to long action types
`js
duckFace.actionTypes.EAT_FISH
// => 'pool-name/duck-name/EAT_FISH'
`$3
Calls registered action creator by its name
`js
dispatch(duckFace.action.eatFish())
`$3
Maps short action type to long action type
`js
duckFace.mapActionType('ACTION_TYPE')
// => 'pool-name/duck-name/ACTION_TYPE'
`$3
Returns all known short action types. Type is known if
.mapActionType or .action was called with it.`js
duckFace.listActionTypes()
// => ['EAT_FISH', 'QUACK']
`$3
Calls registered selector
`js
const state = { counter: 10 }
duckFace.select.counter(state)
// => 10
`$3
Calls duck root reducer
`js
const prepareFish = myDuck.action(null, 'PREPARE_FISH')
myDuck.reducer('EAT_FISH', (state, action, duckFace) => {
const preparedState = duckFace.reduce(state, prepareFish())
return {
...preparedState,
// ...
}
})
`$3
duckFace can also be accessed from the duck itself by
duck.duckFace name.Context
$3
Duck can be created with duckContext that will be accessible for selectors and reducers through duckFace
`js
const themeDuck = Duck('theme', 'my-app', { highlightColor: 'blue' })
themeDuck.duckFace.duckContext.highlightColor
// => 'blue'
`$3
Duck context can be replaced with duck.updateContext(newContext)
`js
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.
`js
const baseDuck = Duck('base', 'my-app')
baseDuck.reducer('BASE_ACTION', / ... /)const extendedDuck = baseDuck.clone('extended', 'my-app')
extendedDuck.reducer('ANOTHER_ACTION', / ... /)
``* @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