jesseskinner / hover

A very lightweight data store with action reducers and state change listeners.
MIT License
98 stars 7 forks source link

Composition #16

Closed jesseskinner closed 9 years ago

jesseskinner commented 9 years ago

It's always been challenging to combine multiple stores. The only way we could this achieve this is by adding a bunch of subscribers to each store, and then triggering actions on the main store to combine the data. I have an idea for a feature that could be built into Hoverboard to make this much easier.

For example, let's say you have a game.

var scoreStore = Hoverboard({
    init: function (state, initialScore) {
        return initialScore;
    },
    add: function (state, score) {
        return state + score;
    }
});

var healthStore = Hoverboard({
    init: function (state, initialHealth) {
        return initialHealth;
    },
    hit: function (state, amount) {
        return state - amount;
    }
});

You might want to combine these stores into a single GameStore for your game.

Currently, you'd have to do this:

var gameStore = Hoverboard({
    setScore: function (state, score) {
        return { score: score };
    },
    setHealth: function (state, health) {
        return { health: health };
    }
});

// subscribe to both stores
healthStore.getState(function (health) {
    gameStore.setHealth(health);
});
scoreStore.getState(function (score) {
    gameStore.setScore(score);
});

So this works, but what I'd like to do is make it easier to combined/compose different stores into a single store. Here's what I'm thinking:

var gameStore = Hoverboard.compose({
    score: scoreStore,
    health: healthStore
});

That's much simpler to understand and to write, and I think it'd make life much easier using Hoverboard.

Some other notes:

var gameStore = Hoverboard.compose({
    score: scoreStore,
    character: Hoverboard.compose({
        health: healthStore
    })
});
var asyncStore = Hoverboard.compose({
    user: function (setState) {
        loadUserFromServer(function (error, user) {
            setState(user);
        });
    }
});
jesseskinner commented 9 years ago

While looking at a way to simplify the TodoMVC example application, I came up with a slight addition to Hoverboard.compose that'll make this much more powerful.

What if you could pass any number of "translate" functions to Hoverboard.compose after the first argument, and have them translate or map the state automatically?

For example, in TodoMVC, I have a TodoStore for all the todos, and then I created an ActiveStore and CompletedStore which contain only the active/completed todos. Here's how I currently have to achieve this:

// create a simple store definition that allows setting a single list property
var ListStore = {
    list: function (state, list) {
        return { list: list };
    }
};

// create stores to contain the active and completed todos
var ActiveStore = Hoverboard(ListStore);
var CompletedStore = Hoverboard(ListStore);

TodoStore.getState(function (state) {
    var all = state.list;

    // when the TodoStore changes, set the lists in these two stores with filtered lists
    ActiveStore.list(_.filter(all, { completed: false }));
    CompletedStore.list(_.filter(all, { completed: true }));
});

Unfortunately, the new Hoverboard.compose does nothing to make this easier. So what if we could just do this instead?

// create stores to contain the active and completed todos
var ActiveStore = Hoverboard.compose(TodoStore, function (state) {
    return {
        list: _.filter(state.list, { completed: false })
    };
});

var CompletedStore = Hoverboard.compose(TodoStore, function (state) {
    return {
        list: _.filter(state.list, { completed: true })
    };
});

This would be incredibly powerful, especially combined with the rest of Hoverboard.compose's functionality. It'd create a reactive approach to Flux, so you could have data flow from any number of sources, and be mapped and translated into the shape you need.

Another great thing, it'll only need a couple extra lines of code added to Hoverboard.compose.