rematch-default-reducers
v1.2.0
Published
Generate reducers for @rematch/core based on your models' intial state
Maintainers
Readme
Rematch Default Reducers
rematch makes working with redux a breeze, but there's still a bit of boilerlate that needs to be automated: reducers (or "actions" as I call them).
If you're tired of writing reducers like setThing, addThing, removeThing,
or resetThing for every single piece of state, then this library is for you.
Installation
npm install --save rematch-default-reducersUsage:
import {init} from '@rematch/core'
import {withDefaultReducers} from 'rematch-default-reducers'
import {tonsOfModelsWithTonsOfState} from '../models'
export default init({
models: withDefaultReducers(tonsOfModelsWithTonsOfState),
})Documentation
Below is the API for withDefaultReducers and the reducers it generates.
withDefaultReducers(models, [opts])
the named and only export of
rematch-default-reducer
models: {[modelName]: {state: any, reducers?: {[reducerName]: function}}}- the
modelsconfig expected byinit()from@rematch/core statemay not containnullorundefinedvalues, or elseTypeErrorwill be thrown.null/undefinedmay be allowed by passing{allowNil: true}asopts
- the
opts?: {allowNil, typeCheck}allowNil?: boolean(default:true) - iffalse, models'statemay containnull/undefined, but reducers will not be generated for those slices of thereduxstoretypeCheck?: boolean(default:true) - iffalse, default reducer actions will not perform type checking to ensurepayloads preserve the type/interface the model was initialized with.
Common Default Reducers
these default reducers are provided for all models
dispatch.${modelName}.set(payload, [meta])
payload:any(required) - the value to set${modelName}.statetometa?: {typeCheck?: boolean}(optional) - options for the currently dispatched actiontypeCheck?: boolean(optional) - enables/disables type-checking that preventssetfrom altering the type/interface of the model. Default:true.
When ${modelName}.state is a {}-Object
setperforms a deep merge betweenstateandpayload.
When ${modelName}.state is NOT a {}-Object
setoverwritesmodel.statewith the value ofpayload
Note: If the
payloadofsetwould alter the type/interface with which${modelName}.statewas initialized, then aTypeErrorwill be thrown, unless{typeCheck: false}has been passed as an option towithDefaultReducersor as ametaoption todispatch.${modelName}.set.
dispatch.${modelName}.reset()
Sets ${modelName}.state to the value it was initialized with.
The rootState Model and Reducers
withDefaultReducersadds a pseudo-model calledrootState. It has nostateof its own, and only exists to provide a couple default reducers for performing updates across multiple models in a single action.
dispatch.rootState.set(payload, [meta])
payload: {[modelName: string]: any}- an updater object which will effectively be deep-merged with thereduxstore in order to produce the next state in a single action.meta?: {typeCheck?: boolean}(optional) - options for the currently dispatched actiontypeCheck?: boolean(optional) - enables/disables type-checking that preventssetfrom altering the type/interface of the model. Default:true.
dispatch.rootState.reset()
Resets all models back to their initial state.
When ${modelName}.state is a {}-Object
When a model's state is a {}-Object, default reducers are generated for each
property of a model's state, in addition to dispatch.${modelName}.set
and dispatch.${modelName}.reset. The reducer names are auto-generated
based on the property's name and follow a camel-case naming convention.
dispatch.${modelName}.set${PropName}(payload, [meta])
payload(type depends on property's intial state) - the value with which to update the the propertymeta?: {typeCheck?: boolean}(optional) - options for the currently dispatched actiontypeCheck?: boolean(optional) - enables/disables type-checking that preventsset${PropName}from altering the type/interface of the model. Default:true.
If the property being set is a {}-Object, then it set the property to the
result of performing a deep merge between the property's current state and
the payload.
Otherwise, it simply overwrites the value of the property with payload.
Note: If the
payloadofset${PropName}would alter the type/interface with which${modelName}.statewas initialized, then aTypeErrorwill be thrown, unless{typeCheck: false}has been passed as an option towithDefaultReducersor as ametaoption todispatch.${modelName}.set${PropName}.
Example:
const {dispatch, getState} = init({
models: withDefaultReducers({
user: {
state: {
name: '',
things: [],
address: {
street: {
primary: '',
secondary: '',
},
city: ''
state: '',
},
},
},
}),
})
dispatch.user.setAddress({
street: { primary: '123 ABC Lane'}
})
dispatch.user.setName('Anderson')
dispatch.user.setThings(['thing1', 'thing2'])
getState().user
/* {
name: 'Anderson',
things: ['thing1', 'thing2'],
address: {
street: {
primary: '123 ABC Lane',
secondary: '',
},
city: ''
state: '',
}
} */dispatch.${modelName}.reset${PropName}
Resets the property to its initial state
Extra Reducers for Array Type Properties
When a property of ${modelName}.state is an Array, that property gets
several other reducers in addition to
set${PropName} and
reset${PropName}.
For the sake of example and readability, let's assume that we have store initialized like so:
const {dispatch, getState} = init({
models: withDefaultReducers({
myModel: {
state: {things: []},
},
}),
})The following reducers would be generated:
dispatch.myModel.concatThings(payload: any[])dispatch.myModel.concatThingsTo(payload: any[])dispatch.myModel.filterThings(payload: { where })dispatch.myModel.insertThing(payload: { where, payload })dispatch.myModel.insertThings(payload: { where, payload })dispatch.myModel.mapThings(mapFn)dispatch.myModel.popThings(n?: number)dispatch.myModel.pushThing(payload: any)dispatch.myModel.removeThing(payload: any)dispatch.myModel.removeThing(payload: { where })dispatch.myModel.removeThings(payload: { where })dispatch.myModel.replaceThing(payload: { where, payload })dispatch.myModel.shiftThings(n?: number)dispatch.myModel.unshiftThing(payload: any)
Take note that some reducers refer to the property name in the singular and some in the plural.
dispatch.myModel.concatThings(payload: any[])
payload: any[]
Sets myModel.state.things to the result of concatentating payload to the end
of myModel.state.things
Example:
store.getState().myModel.things // => ['world']
dispatch.myModel.concatThings(['hello'])
store.getState().myModel.things // => ['world', 'hello']dispatch.myModel.concatThingsTo(payload: any[])
payload: any[]
Sets myModel.state.things to the result of concatenating
myModel.state.things to the end of payload
Example:
store.getState().myModel.things // => ['world']
dispatch.myModel.concatThingsTo(['henlo'])
store.getState().myModel.things // => ['henlo', 'world']dispatch.myModel.filterThings(payload: { where })
payload.where: function(elmt: any, index: number): boolean
Filters myModel.state.things down to the elements that return true when
passed to the predicate function on payload.where along with the element's
index.
Example:
store.getState().myModel.things // ['blah', 4, 'blah]
dispatch.myModel.filterThings({where: (el, i) => typeof el === 'string'})
store.getState().myModel.things // ['blah', 'blah]dispatch.myModel.insertThing(payload: { where, payload })
payload.where: function(elmt: any, index: number): booleanpayload.payload: any
Note singular | Inserts payload.payload at the first index where
payload.where returns true. Pre-existing elements from that index onwards
have their indexes incremented by one.
Example:
getState().myModel.things // => [{name: 'George'}, {name: 'Abe'}]
dispatch.myModel.insertThing({where: (el, i) => i === 1, {name: 'Ben'}})
getState().myModel.things // => [{name: 'George'}, {name: 'Ben'}}, {name: 'Abe'}]dispatch.myModel.insertThings(payload: { where, payload })
payload.where: function(elmt: any, index: number): boolean
payload.payload: any[]
Inserts the contents of payload.payload starting at the first index where
payload.where returns true. Pre-existing elements from that index onwards
have their indexes incremented by the length of payload.payload.
Example:
getState().myModel.things // => [1, 2, 3]
dispatch.myModel.insertThings({where: (el, i) => i === 1, [4, 5, 6]})
getState().myModel.things // => [1, 4, 5, 6, 2, 3]dispatch.myModel.mapThings(mapFn)
mapFn: function(elmt: any, index: number): any
Sets myModel.things to the array returned from mapFn. Behaves mostly like
Array.prototype.map, except it only has arity 1 and mapFn has only arity 2.
Example:
store.getState().myModel.things // => ['henlo', 'world']
dispatch.myModel.mapThings((el, idx) => el.toUpperCase())
store.getState().myModel.things // => ['HENLO', 'WORLD']dispatch.myModel.popThings(n?: number)
n?: number(optional) - number of elements to "pop" from list. Default: 1
Sets myModel.things to a copy of itself with the last n elements removed.
Example:
store.getState().myModel.things // => [1, 2, 3, 4, 5]
dispatch.myModel.popThings()
store.getState().myModel.things // => [1, 2, 3, 4]
dispatch.myModel.popThings(2)
store.getState().myModel.things // => [1, 2]dispatch.myModel.pushThing(payload: any)
payload: any- the value to append to the end of the list
Sets myModel.things to a copy of itself with payload appended as the last
element.
To append multiple elements see dispatch.myModel.concatThings.
Example:
store.getState().myModel.things // => [1, 2, 3]
dispatch.myModel.pushThing(4)
store.getState().myModel.things // => [1, 2, 3, 4]dispatch.myModel.removeThing(payload: any)
payload: any- the value to remove
Sets myModel.things to a copy of itself omitting the first element found to be
strictly equal (===) to payload.
Example:
const [george] = getState().myModel.things // => [{name: 'George'}, {name: 'Abe'}]
dispatch.myModel.removeThing(george)
getState().myModel.things // => [{name: 'Abe'}]dispatch.myModel.removeThing(payload: { where })
payload.where: function(elmt: any, index: number): boolean
Sets myModel.things to a copy of itself omitting the first element for which
payload.where returns true.
Example:
getState().myModel.things // => ["won't", 'you', 'be', 'my', 'neighbor?']
dispatch.myModel.removeThing({where: (el, idx) => el.length < 3})
getState().myModel.things // => ["won't", 'you', 'my', 'neighbor?']dispatch.myModel.removeThings(payload: { where })
payload.where: function(elmt: any, index: number): boolean
Sets myModel.things to a copy of itself omitting all elements for which
payload.where returns true.
Example:
getState().myModel.things // => ["won't", 'you', 'be', 'my', 'neighbor?']
dispatch.myModel.removeThings({where: (el, idx) => el.length < 3})
getState().myModel.things // => ["won't", 'you', 'neighbor?']dispatch.myModel.replaceThing(payload: { where, payload })
payload.where: function(elmt: any, index: number): booleanpayload.payload: any
Sets myModel.things to a copy of itself with the first element for which
payload.where returns true replaced with the value of payload.payload.
Example:
getState().myModel.things // => ["won't", 'you', 'be', 'my', 'neighbor?']
dispatch.myModel.replaceThing({where: (el, idx) => el.length < 3, 'find'})
getState().myModel.things // => ["won't", 'you', 'find', 'my', 'neighbor?']dispatch.myModel.shiftThings(n?: number)
n?: number(optional) - the number of elements to remove from the front of the list. Default: 1
Sets myModel.things to a copy of itself with the first n elements removed.
Example:
getState().myModel.things // => ["won't", 'you', 'be', 'my', 'neighbor?']
dispatch.myModel.shiftThings()
getState().myModel.things // => ['you', 'be', 'my', 'neighbor?']
dispatch.myModel.shiftThings(2)
getState().myModel.things // => ['my', 'neighbor?']dispatch.myModel.unshiftThing(payload: any)
payload: any- the value to prepend to the list
Sets myModel.things to a copy of itself with payload prepended to the list.
To prepend multiple elements, see dispatch.myModel.concatThingsTo
Example:
getState().myModel.things // => ["won't", 'you', 'be', 'my', 'neighbor?']
dispatch.myModel.unshiftThing('Howdy!')
getState().myModel.things // => ['Howdy!', "won't", 'you', 'be', 'my', 'neighbor?']When ${modelName}.state is [] (Array)
When ${modelName}.state is an Array, that model gets several other reducers
in addition to set and
reset.
For the sake of example and readability, let's assume that we have store initialized like so:
const {dispatch, getState} = init({
models: withDefaultReducers({
myModel: {
state: [],
},
}),
})The following reducers that would be generated:
dispatch.myModel.concat(payload: any[])dispatch.myModel.concatTo(payload: any[])dispatch.myModel.filter(payload: { where })dispatch.myModel.insert(payload: { where, payload })dispatch.myModel.insertAll(payload: { where, payload })dispatch.myModel.map(mapFn)dispatch.myModel.pop(n?: number)dispatch.myModel.push(payload: any)dispatch.myModel.remove(payload: any)dispatch.myModel.remove(payload: { where })dispatch.myModel.removeAll(payload: { where })dispatch.myModel.replace(payload: { where, payload })dispatch.myModel.shift(n?: number)dispatch.myModel.unshift(payload: any)
dispatch.myModel.concat(payload: any[])
payload: any[]
Sets myModel.state to the result of concatentating payload to the end of
myModel.state
store.getState().myModel // => ['world']
dispatch.myModel.concat(['hello'])
store.getState().myModel // => ['world', 'hello']dispatch.myModel.concatTo(payload: any[])
payload: any[]
Sets myModel.state to the result of concatenating myModel.state to the end
of payload
store.getState().myModel // => ['world']
dispatch.myModel.concatTo(['henlo'])
store.getState().myModel // => ['henlo', 'world']dispatch.myModel.filter(payload: { where })
payload.where: function(elmt: any, index: number): boolean
Filters myModel.state down to the elements that return true when passed to
the predicate function on payload.where along with the element's index.
Example:
store.getState().myModel // ['blah', 4, 'blah]
dispatch.myModel.filter({where: (el, i) => typeof el === 'string'})
store.getState().myModel // ['blah', 'blah]dispatch.myModel.insert(payload: { where, payload })
payload.where: function(elmt: any, index: number): booleanpayload.payload: any
Note singular | Inserts payload.payload at the first index where
payload.where returns true. Pre-existing elements from that index onwards
have their indexes incremented by one.
getState().myModel // => [{name: 'George'}, {name: 'Abe'}]
dispatch.myModel.insert({where: (el, i) => i === 1, {name: 'Ben'}})
getState().myModel // => [{name: 'George'}, {name: 'Ben'}}, {name: 'Abe'}]dispatch.myModel.insertAll(payload: { where, payload })
payload.where: function(elmt: any, index: number): boolean
payload.payload: any[]
Inserts the contents of payload.payload starting at the first index where
payload.where returns true. Pre-existing elements from that index onwards
have their indexes incremented by the length of payload.payload.
getState().myModel // => [1, 2, 3]
dispatch.myModel.insertAll({where: (el, i) => i === 1, [4, 5, 6]})
getState().myModel // => [1, 4, 5, 6, 2, 3]dispatch.myModel.map(mapFn)
mapFn: function(elmt: any, index: number): any
Sets myModel to the array returned from mapFn. Behaves mostly like
Array.prototype.map, except it only has arity 1 and mapFn has only arity 2.
store.getState().myModel // => ['henlo', 'world']
dispatch.myModel.map((el, idx) => el.toUpperCase())
store.getState().myModel // => ['HENLO', 'WORLD']dispatch.myModel.pop(n?: number)
n?: number(optional) - number of elements to "pop" from list. Default: 1
Sets myModel to a copy of itself with the last n elements removed.
store.getState().myModel // => [1, 2, 3, 4, 5]
dispatch.myModel.pop()
store.getState().myModel // => [1, 2, 3, 4]
dispatch.myModel.pop(2)
store.getState().myModel // => [1, 2]dispatch.myModel.push(payload: any)
payload: any- the value to append to the end of the list
Sets myModel to a copy of itself with payload appended as the last element.
To append multiple elements see dispatch.myModel.concat.
store.getState().myModel // => [1, 2, 3]
dispatch.myModel.pushThing(4)
store.getState().myModel // => [1, 2, 3, 4]dispatch.myModel.remove(payload: any)
payload: any- the value to remove
Sets myModel to a copy of itself omitting the first element found to be
strictly equal (===) to payload.
Example:
const [george] = getState().myModel // => [{name: 'George'}, {name: 'Abe'}]
dispatch.myModel.remove(george)
getState().myModel // => [{name: 'Abe'}]dispatch.myModel.remove(payload: { where })
payload.where: function(elmt: any, index: number): boolean
Sets myModel to a copy of itself omitting the first element for which
payload.where returns true.
Example:
getState().myModel // => ["won't", 'you', 'be', 'my', 'neighbor?']
dispatch.myModel.remove({where: (el, idx) => el.length < 3})
getState().myModel // => ["won't", 'you', 'my', 'neighbor?']dispatch.myModel.removeAll(payload: { where })
payload.where: function(elmt: any, index: number): boolean
Sets myModel to a copy of itself omitting all elements for which
payload.where returns true.
Example:
getState()
.myModel // => ["won't", 'you', 'be', 'my', 'neighbor?']
.dispatch.myModel.remove({where: (el, idx) => el.length < 3})
getState().myModel // => ["won't", 'you', 'neighbor?']dispatch.myModel.replace(payload: { where, payload })
payload.where: function(elmt: any, index: number): booleanpayload.payload: any
Sets myModel to a copy of itself with the first element for which
payload.where returns true replaced with the value of payload.payload.
Example:
getState().myModel // => ["won't", 'you', 'be', 'my', 'neighbor?']
dispatch.myModel.replace({where: (el, idx) => el.length < 3, 'find'})
getState().myModel // => ["won't", 'you', 'find', 'my', 'neighbor?']dispatch.myModel.shift(n?: number)
n?: number(optional) - the number of elements to remove from the front of the list. Default: 1
Sets myModel to a copy of itself with the first n elements removed.
Example:
getState().myModel // => ["won't", 'you', 'be', 'my', 'neighbor?']
dispatch.myModel.shift()
getState().myModel // => ['you', 'be', 'my', 'neighbor?']
dispatch.myModel.shift(2)
getState().myModel // => ['my', 'neighbor?']dispatch.myModel.unshift(payload: any)
payload: any- the value to prepend to the list
Sets myModel to a copy of itself with payload prepended to the list.
To prepend multiple elements, see dispatch.myModel.concatTo
Example:
getState().myModel // => ["won't", 'you', 'be', 'my', 'neighbor?']
dispatch.myModel.unshift('Howdy!')
getState().myModel // => ['Howdy!', "won't", 'you', 'be', 'my', 'neighbor?']