Created
February 8, 2017 18:41
-
-
Save milankinen/1324c105c944978d7ad9ac7f69740f79 to your computer and use it in GitHub Desktop.
CULLI state (de)composition
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as O from "most" | |
import * as L from "partial.lenses" | |
import DOM from "@culli/dom" | |
import Store, {Memory, byType} from "@culli/store" | |
import {run} from "@cycle/most-run" | |
// partial.lenses lens => culli lens | |
const P = (pl) => ({ | |
get: L.get(pl), | |
set: L.set(pl) | |
}) | |
run(App, { | |
DOM: DOM("#app"), | |
Store: Store(Memory({ | |
text: "tsers!", | |
notSoNestedState: 10, | |
some: { | |
nested: { | |
state: 1 | |
} | |
} | |
})) | |
}) | |
function App(sources) { | |
function model(state) { | |
// we haven't decomposed the "root state" yet, so we must modify | |
// text property | |
const dispatch = state.actions.reduce(byType({ | |
["SET_TEXT"]: (state, newText) => ({...state, text: newText}) | |
})) | |
// however, the exactly identical behaviour could have been achieved | |
// with the following code: | |
// const dispatch = state.value.select("text").actions.reduce((text, {payload: newText}) => newText) | |
// now let's "decompose state into smaller parts now" | |
const text = state.value.select("text") | |
const counters = state.value.select(P(L.pick({ | |
counter1: "notSoNestedState", | |
counter2: ["some", "nested", "state"] | |
}))) | |
return { | |
dispatch, | |
props: { counters, text, entireState: state } | |
} | |
} | |
function view({counters, text, entireState}) { | |
// now let's pass the decomposed "counters" to our "general-purpose" component | |
// that expects {counter1, counter2} | |
const children = Counters({...sources, Store: counters}) | |
const vdom = h("div", [ | |
h("div", [ | |
h("h1", "Entire state is:"), | |
h("pre", [entireState.value.map(s => JSON.stringify(s, null, 2))]) | |
]), | |
h("label", [ | |
"Some text: ", | |
h("input.text", {type: "text", value: text.value}) | |
]), | |
h("div", [ | |
children.DOM | |
]), | |
]) | |
return {vdom, children} | |
} | |
function intent(vdom) { | |
return vdom.on(".text", "input").map(e => ({type: "SET_TEXT", payload: e.target.value})) | |
} | |
const {DOM: {h, combine}, Store} = sources | |
const {dispatch, props} = model(Store) | |
const {vdom, children} = view(props) | |
const actions = intent(vdom) | |
return { | |
DOM: combine(vdom), | |
// and here we "compose" actions by using dispatch function and normal merge operator | |
Store: O.merge(dispatch(actions), children.Store) | |
} | |
} | |
function Counters(sources) { | |
const {DOM: {h, combine}, Store: state} = sources | |
// and here again, we can use the state and "decompose" it again and don't worry much... | |
const a = state.value.select("counter1") | |
const b = state.value.select("counter2") | |
// ...and pass those decomposed states to child components :-) | |
const counterA = Counter({...sources, title: "A", Store: a}) | |
const counterB = Counter({...sources, title: "B", Store: b}) | |
return { | |
DOM: combine(h("div", [ | |
h("h1", "Counters state:"), | |
h("pre", [state.value.map(s => JSON.stringify(s, null, 2))]), | |
h("div", [ | |
counterA.DOM, | |
counterB.DOM | |
]) | |
])), | |
// ...and again compose mods into bigger one | |
Store: O.merge(counterA.Store, counterB.Store) | |
} | |
} | |
function Counter({title, DOM: {h, combine}, Store: state}) { | |
// ...and again, in order to make state modifications, we must use reduce + dispatch | |
const dispatch = state.actions.reduce(byType({ | |
["INC"]: state => state + 1, | |
["DEC"]: state => state - 1 | |
})) | |
const vdom = h("div", [ | |
h("h2", ["Counter ", title, " (", state.value, ")"]), | |
h("button.inc", "+"), | |
h("button.dec", "-") | |
]) | |
const actions = O.merge( | |
vdom.on(".inc", "click").map(() => ({type: "INC"})), | |
vdom.on(".dec", "click").map(() => ({type: "DEC"})) | |
) | |
return { | |
DOM: combine(vdom), | |
Store: dispatch(actions) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment