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