README
THIS PACKAGE IS LEGACY, PLEASE UPDATE TO @duckness/duck
duckness
Duckness helps you to build ducks - redux modules.
Example
// counterDuck.js
import Duck from 'duckness'
// Create duck with the name 'counter' for 'counter-app'
const counterDuck = Duck('counter', 'counter-app')
// Export action creators
counterDuck.action('incrementCounter', 'INCREMENT')
// counterDuck.act.incrementCounter will build actions with type 'counter-app/counter/INCREMENT'
counterDuck.action('decrementCounter', 'DECREMENT')
// counterDuck.act.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
- duckness
- Example
- Table of Contents
- API
- Additional resources
API
Create Duck
Create a new duck with duckName and poolName (poolName is a namespace for ducks)
import Duck from 'duckness'
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.act.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 }
.act[]
Calls registered action creator by its name
const eatFish = myDuck.action('eatAllTheFish', 'EAT_FISH')
myDuck.act.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']
.types[]
Is an object that maps known short action types to long action types
myDuck.types.EAT_FISH
// => 'pool-name/duck-name/EAT_FISH'
Selectors
.selector(selectorName, selector)
Registers selector under selectorName
myDuck.selector('counter', state => (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) => {
return {
...state,
fishEaten: state.fishEaten + action.payload.amount
}
})
.reducer(null, reducer)
Registers wildcard reducer for all duck action types
myDuck.reducer(null, (state, action) => {
return {
...state,
updated: (state.updated || 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.types[]
Is an object that maps known short action types to long action types
duckFace.types.EAT_FISH
// => 'pool-name/duck-name/EAT_FISH'
duckFace.act[]
Calls registered action creator by its name
dispatch(duckFace.act.eatFish())
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.face
duckFace can also be accessed from the duck itself by duck.face
name.
Clone duck
Duck can be cloned by calling .clone(duckName, moduleName)
.
Cloned duck will contain selectors, reducers and known action types copied from original duck with
all action types adjusted to duckName
and moduleName
. 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', /* ... */)
Hatch
Calling .hatch()
on duck will freeze it preventing duck from modifications like adding reducers or selectors.
Hatched duck can be cloned and clone is unhatched.
Additional resources
- duckness-saga - Saga extension for Duckness
- duckness-pool - boilerplate for React-Redux apps based on Duckness and Duckness-Saga