-
-
Save milankinen/3a5c6bfba11e6f8fdece to your computer and use it in GitHub Desktop.
import React from "react" | |
import {Subject, Observable} from "rx" | |
import {render} from "react-dom" | |
import {Combinator} from "react-combinators/rx" | |
import {createDispatcher} from "fluorine-lib" | |
const initialBMI = {weight: 80, height: 180} | |
function BMI(state, action) { | |
state = state || initialBMI | |
return action.type === "SET" ? {...state, [action.key]: action.value} : state | |
} | |
function Model() { | |
const dispatcher = createDispatcher() | |
const model = dispatcher.reduce(BMI).shareReplay() | |
const weight = model.map(m => m.weight).share() | |
const height = model.map(m => m.height).share() | |
const bmi = Observable | |
.combineLatest(weight, height) | |
.map(([w, h]) => Math.round(w / (h * h * 0.0001))) | |
.share() | |
return { | |
weight, height, bmi, | |
setWeight: value => dispatcher.dispatch({type: "SET", key: "weight", value}), | |
setHeight: value => dispatcher.dispatch({type: "SET", key: "height", value}) | |
} | |
} | |
function App() { | |
// and here we can use the same object like any other object | |
const { setHeight, setWeight, height, weight, bmi } = Model() | |
return ( | |
<Combinator> | |
<div> | |
<h1>BMI counter example</h1> | |
{renderSlider("Height", height, setHeight, 100, 240)} | |
{renderSlider("Weight", weight, setWeight, 40, 150)} | |
{/* and here we can embed the observables directly into the JSX */} | |
Your BMI is: <span className="bmi">{bmi}</span> | |
</div> | |
</Combinator> | |
) | |
} | |
function renderSlider(title, value, setValue, min, max) { | |
return ( | |
<div> | |
{title}: {value} <br /> | |
<input type="range" min={min} max={max} value={value} className={title} | |
onChange={e => setValue(e.target.value)} /> | |
</div> | |
) | |
} | |
render(<App />, document.getElementById("app")) |
@milankinen I've just tried the counter example that I've got on the fluorine repo with a combinator. Simple enough I've just dropped it at the top level, which didn't work, but I'll have to investigate that. Dropping it a level deeper worked and there's no notable performance difference. (Maybe it was just the derp though? :D)
This is a very simple example and I have to check how it holds up against bigger codebases. But dropping the observables directly into the jsx is very freeing, as the withStore
decorator can be removed completely.
Then again the decorator saves the code from doing another breadth search of all nodes - even though that's an implementation detail and probably not inefficient.
But at the end of the day the difference in the example would comes down to usage, which looks probably like this in the end:
@withStore(dispatcher.reduce(reducer).map(x => x.get('value')), 'value')
class Component {
render() {
return (
<div>
{this.props.value}
</div>
)
}
}
vs
const value = dispatcher.reduce(reducer).map(x => x.get('value'))
class Component {
render() {
return (
<div>
{value}
</div>
)
}
}
The difference is probably crucial when you eliminate as much component state as possible and just use pure functions as components, which is probably a nice use case as well, but which unfortunately can also be solved by using a decorator:
let component = ({value}) => (
<div>
{value}
</div>
)
component = withStore(dispatcher.reduce(reducer).map(x => x.get('value')), 'value')(component)
export default component
vs
const value = dispatcher.reduce(reducer).map(x => x.get('value'))
export default const component = () => (
<div>
{value}
</div>
)
This is now a clear improvement, but still I'm not sure if it's worth the price ^^
I'd love to hear more comments from you on this :)
This looks pretty nice. :) I'm wondering how a complete example would look. I'll probably build something like that soon to see whether it is a nice replacement for decorators and top-level components.
How's the Combinator's performance? Is it worse than top level components? Does it traverse the virtual dom every time an observable emits a new value?