Skip to content

Instantly share code, notes, and snippets.

@milankinen
Last active November 3, 2015 12:40
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save milankinen/c867a7d2b0493f007cb7 to your computer and use it in GitHub Desktop.
Pekonimmmmehustelua
import React from "react"
import Bacon from "baconjs"
import {render} from "react-dom"
import liftVDOM from "./liftVDOM"
import combineAsComponent from "./combineAsComponent"
import Atom from "./atom"
function initApp() => {
const bmiModels = Atom([BMIModel()])
const bmiComponents =
bmiModels.map(models => models.map(m => <BMI model={m} />))
// just for fun
bmiModels
.map(models => models.map(Bacon.combineTemplate))
.flatMapLatest(Bacon.combineAsArray)
.log("all BMIs")
return liftVDOM(
<div>
<h1>BMI counterz</h1>
<div>{bmiComponents}</div>
<hr />
<button onClick={() => bmiModels.swap(models => [...models, BMIModel()])}>
Add
</button>
</div>
)
}
const BMIModel = () => {
const weight = Atom(70)
const height = Atom(170)
const bmiP = Bacon.combineWith(weight, height, (w, h) => Math.round(w/(h * h * 0.0001)))
return {weight, height, bmiP}
}
const bmi = ({weight, height, bmiP}) =>
liftVDOM(
<div>
<hr />
{slider("Weight", "kg", 40, 140, weight)}
{slider("Height", "cm", 140, 210, height)}
BMI: {bmiP}
</div>
)
const BMI = combineAsComponent(({model}) => model.flatMapLatest(bmi))
const slider = (title, units, min, max, value) =>
liftVDOM(
<div>
{title}: {value} {units}
<div>
<input type="range" min={min} max={max} value={value}
onChange={e => value.reset(e.target.value)}/>
</div>
</div>
)
initApp().onValue(app => render(app, document.getElementById("app")))
import Bacon from "baconjs"
export default function Atom(initialValue) {
const bus = new Bacon.Bus()
const atom = bus.scan(initialValue, (state, fn) => fn(state))
atom.reset = val => bus.push(_ => val)
atom.swap = fn => bus.push(fn)
return atom
}
import Bacon from "baconjs"
import React from "react"
import {keys, values, zip, zipObject} from "lodash"
/**
* Wraps Observable render function [(...args) => Observable] into React.Component so that
* properties that are passed from parent elements are transformed into EventStreams
* (e.g. (prop1, prop2) => (Observable(prop1), Observable(prop2)).
*
* Returned React.Component can be used like any other React.Component
*
* Example:
* (see liftVDOM first)
*
* const App = React.createClass({
* render() {
* const {width, height} = this.state // change this somehow
* return (
* <div>
* Example
* <BMI width={width} height={height} />
* </div>
* )
* }
* })
*
* const BMI = combineAsComponent({width, height} => {
* const bmiP = Bacon.combineWith(weight, height, (w, h) => Math.round(w/(h * h * 0.0001)))
* return liftVDOM(
* <div>BMI for {width} and {height} is {bmiP}</div>
* )
* })
*/
export default function combineAsComponent(renderFn) {
return React.createClass({
getInitialState() {
const propsBuses = zipObject(keys(this.props), values(this.props).map(val => new Bacon.Bus()))
const propsS =
zipObject(keys(this.props), zip(values(propsBuses), values(this.props)).map(([bus, initial]) => (
bus.startWith(initial).skipDuplicates()
)))
return {
propsBuses,
vdomS: renderFn(propsS),
vdom: null
}
},
componentWillMount() {
const updateVDOM = vdom => this.setState({vdom})
if (process.browser) {
this.setState({ dispose: this.state.vdomS.onValue(updateVDOM) })
} else {
this.state.vdomS.take(1).onValue(updateVDOM)
}
},
componentWillReceiveProps(nextProps) {
keys(nextProps).forEach(propName => {
const bus = this.state.propsBuses[propName]
if (!bus) {
console.warn(
`Trying to pass property "${propName}" that is not set during the component creation.`,
`Ignoring this property.`
)
} else {
bus.push(nextProps[propName])
}
})
},
shouldComponentUpdate(nextProps, nextState) {
return nextState.vdom !== this.state.vdom
},
componentWillUnmount() {
const {dispose} = this.state
if (dispose) {
dispose()
}
},
render() {
return this.state.vdom
}
})
}
import Bacon from "baconjs"
import React from "react"
import {cloneAndReplaceProps, isValidElement} from "react/lib/ReactElement"
import {find, isEmpty, isArray} from "lodash"
/**
* Transforms: VDOM(Observable) => Observable(VDOM)
* Example:
* const a = Bacon.constant(4)
* const b = Bacon.constant(3)
* const c = Bacon.combineWith(a, b, (a, b) => a + b)
*
* liftVDOM(<div>{a} + {b} = <span className="result">{c}</span></div>)
*/
export default function liftVDOM(vdom) {
const obs = resolveObservables(vdom, [])
if (isEmpty(obs)) {
return Bacon.constant(vdom)
} else {
return Bacon
.combineAsArray(obs)
.map(values => assignObservableValues(vdom, values))
}
function resolveObservables(el, obs) {
const propKeys = Object.keys(el.props || {})
for (let k = 0 ; k < propKeys.length ; k++) {
const key = propKeys[k]
const prop = el.props[key]
if (key === "children") {
const children = isArray(prop) ? prop : [ prop ]
for (let i = 0 ; i < children.length ; i++) {
const child = children[i]
if (child instanceof Bacon.Observable && !find(obs, o => o.obs === child)) {
obs.push(child.map(value => ({obs: child, value})))
} else if (isValidElement(child)) {
resolveObservables(child, obs)
}
}
} else {
if (prop instanceof Bacon.Observable && !find(obs, o => o.obs === prop)) {
obs.push(prop.map(value => ({obs: prop, value})))
}
}
}
return obs
}
function assignObservableValues(el, obsValues) {
const newProps = {}
const propKeys = Object.keys(el.props || {})
for (let k = 0 ; k < propKeys.length ; k++) {
const key = propKeys[k]
const prop = el.props[key]
if (key === "children") {
const children = isArray(prop) ? prop : [ prop ]
const newChildren = []
for (let i = 0 ; i < children.length ; i++) {
const child = children[i]
if (child instanceof Bacon.Observable) {
newChildren.push(find(obsValues, r => r.obs === child).value)
} else if (isValidElement(child)) {
newChildren.push(assignObservableValues(child, obsValues))
} else {
newChildren.push(child)
}
}
newProps.children = isArray(prop) ? newChildren : newChildren[0]
} else {
if (prop instanceof Bacon.Observable) {
newProps[key] = find(obsValues, r => r.obs === prop).value
} else {
newProps[key] = prop
}
}
}
return cloneAndReplaceProps(el, newProps)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment