.map() plugin for @set-state
By Paul Grenier (@AutoSponge)
@set-state/map plugs into a factory or state function. Any node created will have the .map() function.
npm install --save @set-state/core @set-state/map
// import factory, {state} from '@set-state/core'
// import mapPlugin from '@set-state/map'
state.use(mapPlugin)Calling node.map(predicate) creates a "sealed" node based on the return value of the predicate function. The predicate recieves the node's value as a parameter.
Mapped nodes update synchronously if their predicate returns a non-Promise value.
const str = state('beep')
const bang = str.map(val => `${val}!`)
bang() // => 'beep!'
bang('foo') // bang is sealed, so this is ignored. Safety first!
bang() // => 'beep!'Calling node.map(predicate, errorHandler) creates a "sealed" node that will update its value when the predicate resolves. If the predicate rejects or throws, the errorHandler will be called passing the error as a parameter.
This example demonstrates how you can use .map asynchronously.
const selectedItem = state()
const selectedItemData = selectedItem.map(id => {
if (!id) return {msg: 'select an item'} // sync value
return fetch(`/api/items/${id}`) // async value
.then(res => res.json())
}, renderError)
selectedItemData.on(render)Note:
.map() has two additional features for async predicates to prevent memory leaks, update storms, and out-of-sync state.
First, similar to a "debounce", mapped nodes will not invoke the predicate while awaiting a new value from a previous invocation.
Second, when the mapped node updates value, if the cached node.value differs from the current node.value, it will call the predicate again with the latest value attempting to "fast forward" to the correct state.
const a = state(0)
const b = a.map(n => n + 1)
b
/*
{ [Function: f]
[...omitted]
locals: {
map: { <== map's cache object
value: 0, <== value of a used to invoke the predicate
awating: false <== update was synchronous
}
},
map: [Function], <== the map method
value: 1 <== current value of b
}
*/