anthonyshort / deku

Render interfaces using pure functions and virtual DOM
https://github.com/anthonyshort/deku/tree/master/docs
3.41k stars 130 forks source link

baobab-deku #362

Open Yomguithereal opened 8 years ago

Yomguithereal commented 8 years ago

Hello deku. I've just started implementing a very simple helpers lib to use deku along with Baobab. You can think of it as a centralized app state store.

I am just starting a discussion here about the choices made and so I can maybe collect some feedback. It just has a createDispatcher function and very simple actions (that you can easily compose) and a branch function which is a higher order component and binds props of the given components to some part of the tree.

Yomguithereal commented 8 years ago

Here is the gist:

import Baobab from 'baobab';
import {dom, element} from 'deku';
import {createDispatcher, branch} from 'baobab-deku';

const {createRenderer} = dom;

// 1. Creating our tree
const tree = new Baobab({counter: 0});

// 2. Creating actions to mutate the counter
function increment(tree, by = 1) {
  tree.apply('counter', nb => nb + by);
}

function decrement(tree, by = 1) {
  tree.apply('counter', nb => nb - by);
}

// 3. Creating our dispatcher & renderer
const dispatcher = createDispatcher(tree),
      render = createRenderer(document.body, dispatcher);

// 4. Creating a counter component
const Counter = branch({counter: ['counter']}, ({dispatch, props}) => {
  return (
    <div>
      <p>{props.counter}</p>
      <div>
        <button onClick={dispatch(decrement)}>-</button>
        <button onClick={dispatch(increment)}>+</button>
      </div>
      <div>
        <button onClick={dispatch(decrement, 10)}>-10</button>
        <button onClick={dispatch(increment, 10)}>+10</button>
      </div>
    </div>
  );
});

// 5. Rendering our app, pass the tree as context & refreshing on tree update
function refresh() {
  render(<Counter />, {tree});
}

tree.on('update', refresh);
refresh();
troch commented 8 years ago

:+1: looks nice

ashaffer commented 8 years ago

This looks interesting. I think an approach like this using cursors might be the right one. However, it seems like a weird definition of dispatch that you have here.

Why not dispatch into something like redux, and have your baobab tree updated in your reducer?

Yomguithereal commented 8 years ago

You could very dispatch data-actions like redux does and reduce them but the thing is Baobab enables imperative style updates and the actions aren't pure per se. It works more like Clojure's atom swapping like Om does, for instance.

One of the reason I went this way is because hot-reloading is free. You just need to hot-reload the rendering logic whereas with redux, as displayed here, you need to hot-reload both the rendering logic and the reducers.

Yomguithereal commented 8 years ago

Another solution, which is used by baobab-react is to partially apply the actions with the tree at component level, but this makes the higher order branch more difficult to use for virtually no benefit.

rstacruz commented 8 years ago

this is neat, I'd probably have tried to pass the tree to render(<Counter />, tree) and made branch() work like react-redux's connect() or something.

Yomguithereal commented 8 years ago

Thanks @rstacruz. I forgot to pass the tree in context indeed.

Yomguithereal commented 8 years ago

What do you mean to work like react-redux's connect()?

rstacruz commented 8 years ago

react-redux has a connect() function that lets you select what part of the humongous state tree you want to use in the component:

module.exports = connect(map)(component)

function map (state) {
  // return a slice of `state` that the component concerns itself with.
  // it will be passed as props to the component.
  return { number: state.number }
}
Yomguithereal commented 8 years ago

You don't need this with Baobab because the tree has cursors (which is very useful to handle humongous state trees).

This is what is done by:

const Counter = branch({counter: ['counter']}, Component);

It basically says, I want a prop to be named counter and map data from the tree at path ['counter'] to it.

Plus, this is statically analyzable and enables (not yet with deku but with React it does) a re-render directly from relevant components.

anthonyshort commented 8 years ago

Might be good to get this into the docs to show an approach using immutable structures instead of Redux :)