atomiperffiä
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 React from "react" | |
import Bacon from "baconjs" | |
import {render} from "react-dom" | |
import {range, sum} from "lodash" | |
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 | |
.flatMapLatest(models => Bacon.combineAsArray(models.map(m => m.bmiP))) | |
.map(bmis => sum(bmis) / bmis.length) | |
.log("avg bmi") | |
return Bacon.combineTemplate( | |
<div> | |
<h1>BMI counterz ({bmiModels.map(".length")})</h1> | |
<hr /> | |
<button onClick={() => bmiModels.swap(models => [...models, ...range(1000).map(_ => BMIModel())])}> | |
Add | |
</button> | |
<div>{bmiComponents}</div> | |
</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}) => | |
Bacon.combineTemplate( | |
<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) => | |
Bacon.combineTemplate( | |
<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("todoapp"))) |
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 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 | |
} |
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 Bacon from "baconjs" | |
import React from "react" | |
import {keys, values, zip, zipObject} from "lodash" | |
/** | |
* Wraps function (...args) => Observable into React.Component so that | |
* properties that are passed from parent elements are transformed to EventStreams | |
* (e.g. (prop1, prop2) => (Observable(prop1), Observable(prop2)) | |
* | |
* Example: | |
* | |
* 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 Bacon.combineTemplate( | |
* <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 | |
} | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment