Simple state management with xstream
and ramda
, in more transparent fashion than onionify
import * as R from 'ramda'
// first we create factory for making special state stream
// that will hold our stream value and will be modified with supplied streams of reducers
type StateReducer<T> = (state: T) => T
type StateStreamReduce<T> = (...reducerStreams: Stream<StateReducer<T>>[]) => void
type StateStream<T> = Stream<T> & { reduce: StateStreamReduce<T> }
const createState = <T>(initialState?: T): StateStream<T> => {
let reducer$ = xs.create<StateReducer<T>>();
let state$ = <any>reducer$
.fold<T>((state, reducer) => reducer(state), initialState!)
// let's polute state$ with special method that will take streams of reducers,
// that will be used to modify state
state$.reduce = (...reducersStreams: Stream<StateReducer<T>>[]) => {
reducer$.imitate(xs.merge(...reducersStreams))
}
return state$
}
const Main = ({...}: Sources): Sinks => {
// create state stream with innitial state value (optionally)
let state$ = createState({count: 0, child: {some: 'data'}})
// here is some child component that supposed to deal "child" state part.
// Using `dropRepeats` will ensure that new value on childState$ will only be emitted when
// object's value under "child" property changed (this is the case when we modify state
// keeping its immutability using Ramda)
let childState$ = state$.map(R.prop('child')).compose(dropRepeats())
let child = Child({ state$: childState$ }
state$.reduce(
// lets increase state's "count" property
xs.of(R.over(R.lensProp('count'), R.inc)),
// Our child will aslso return `reducer$` stream that we should apply to "child" state part.
// Using Ramda lenses we make elegantly apply child's reducer to "child" property
child.reducer$.map(R.over(R.lensProp('child')))
)
return {
....
}
}
This is actually just an idea for implementation. You may invent what ever you want on top of it.
For working with other libs (rx
, most
) you may try to implement circular dependency for reducer$
stream with cycle-proxy