Skip to content

Instantly share code, notes, and snippets.

@milankinen
Created January 25, 2016 16:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save milankinen/3a5c6bfba11e6f8fdece to your computer and use it in GitHub Desktop.
Save milankinen/3a5c6bfba11e6f8fdece to your computer and use it in GitHub Desktop.
fluorine+combinators
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"))
@kitten
Copy link

kitten commented Jan 25, 2016

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?

@kitten
Copy link

kitten commented Jan 25, 2016

@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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment