redux-pixies
Version:
The magical asynchronous Redux library
108 lines (95 loc) • 3.08 kB
Flow
// @flow
import { tamePixie } from '../enhancers/tamePixie.js'
import type {
PixieInstance,
TamePixie,
TamePixieInput,
WildPixie
} from '../types.js'
import { catchify, shallowCompare } from './util.js'
/**
* Combines one or more pixies into one, using a list of keys.
*/
export function mapPixie<P, Q>(
pixie: WildPixie<Q>,
listIds: (props: P) => string[],
filter: (props: P, id: string) => Q | void
): TamePixie<P> {
const tamedPixie = tamePixie(pixie)
function outPixie(input: TamePixieInput) {
const { onError } = input
let instances: { [id: string]: PixieInstance<Q> } = {}
let outputs: { [id: string]: any } = {}
let outputsDirty: boolean = false
const propsCache: { [id: string]: Q | void } = {}
let updating: boolean = false
let destroyed: boolean = false
const safeListIds = catchify(listIds, onError)
const safeFilter = catchify(filter, onError)
const updateOutputs = () => {
if (outputsDirty && !updating) {
outputsDirty = false
const newOutputs = {}
for (const id of Object.keys(instances)) {
newOutputs[id] = outputs[id]
}
outputs = newOutputs
input.onOutput(outputs)
}
}
return {
update(outerProps: P) {
const ids = safeListIds(outerProps)
if (destroyed || !ids) return
// Update or create instances for all keys:
updating = true
const newInstances: { [id: string]: PixieInstance<Q> } = {}
for (const id of ids) {
const innerProps = safeFilter(outerProps, id)
if (destroyed) return
const dirty = !shallowCompare(innerProps, propsCache[id])
propsCache[id] = innerProps
if (innerProps) {
if (!instances[id]) {
const onOutput = (data: any) => {
if (data !== outputs[id]) {
outputs[id] = data
outputsDirty = true
updateOutputs()
}
}
instances[id] = tamedPixie({ onError, onOutput })
if (destroyed) return
}
if (dirty) instances[id].update(innerProps)
if (destroyed) return
newInstances[id] = instances[id]
}
}
// Swap out the instance list, removing unwanted pixies:
const oldInstances = instances
instances = newInstances
// Destroy any old instances that are not on the list.
// We need to finish this even if it triggers `destroyed`:
for (const id of Object.keys(oldInstances)) {
if (!newInstances[id]) {
outputsDirty = true
oldInstances[id].destroy()
}
}
if (destroyed) return
updating = false
updateOutputs()
},
destroy() {
destroyed = true
for (const id of Object.keys(instances)) {
if (instances[id]) instances[id].destroy()
}
}
}
}
outPixie.tame = true
outPixie.defaultOutput = {}
return outPixie
}