Giter Site home page Giter Site logo

hover's People

Contributors

bfred-it avatar jesseskinner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hover's Issues

Allow stores to implement state cloning/copying or other immutable protections via getState

Currently the state object returned by getState and this.state is created by making a serialized copy with this:

JSON.parse(JSON.stringify(state));

Users might want to use an immutable data structure instead. They might want to allow functions or use Object.assign to clone the state. Or any other combination of things.

To keep the API simple, we could just allow each store to define a getState() method to implement it's own cloning (or immutable object wrapping or whatever). It might look something like this:

Hoverboard({
    getState: function(state){
        return JSON.parse(JSON.stringify(state));
    }
});

If we do that, then the question is: should Hoverboard stop cloning the state by default with JSON serialization, and be agnostic by default with a mutable state?

react context

Looking at geiger I saw that context are used in an interesting fashion.
https://github.com/netgusto/Geiger

There is also a blog post about react context. https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html (old)

It would be great to document how to use it similar to how the waitFor was documented.

Here is a use case:
Assume you have a widget (component) that would want to share. Let us take twitter feed as an example. It is a pretty complex component so you would want to have a custom store for each instance of the component.

var twitterstore = Hoverboard(TwitterStore);
<Context twitterstore={twitterstore}>
   <TwitterFeed />
</Context>

var TwitterFeed = React.createClass({
   static contextTypes: {
       twitterstore: React.PropTypes.object.isRequired
   },
   componentDidMount() {
      this.twitterstore.getState(state => this.setState({ tweets: tweet }));
   },
    render() {
        return this.state.tweets.map(t => <Tweet tweet={t} />);
    }
};

var Tweet = React.createClass({
   static contextTypes: {
       twitterstore: React.PropTypes.object.isRequired
   },
   onRewteet(e) {
       e.preventDefault();
       this.context.twitterstore.Retweet(this.props.tweet.id);
   },
   render() {
        return <div>{this.props.tweet.text} <input type="button" onClick={this.onRewteet} value="Retweet" /></div>
    }
};

Assume that now the twitterstore is per user. I can now have multiple TwitterFeed component that works independently in the same page.

var userATwitterStore = Hoverboard(TwitterStore);
userATwitterStore.setUserId('A');
<Context twitterstore={userATwitterStore }>
   <TwitterFeed />
</Context>

var userBTwitterStore = Hoverboard(TwitterStore);
userBTwitterStore.setUserId('B');
<Context twitterstore={userBTwitterStore }>
   <TwitterFeed />
</Context>

Stores that depend on other stores?

Hi,

How would you go about handling the situation where a sub-store-action has a dependency on something else.

For example I have a logger on my base store state and I want to access it in a sub-store state how do I go about doing that?

Or am I missing something?

Cheers,
Mike

Typings?

Hey,

Great job on the library, it looks really simple and clean to use.

Im a TS user and it would be awesome to get some typings. I have started to write my own but im not too good at it and having issues.

Mike

Single getter paradigm seems too constraining

Sniffing around at flux implementations here. Hoverboard's ultra minimalist philosophy seems appealing, but onViewById/updateItem example seems like a not-great design. Would work in some cases but very confining.

Also, json serialize/deserialize seems a high to provide a read-only copy of the whole state. Could be avoided by letting users expose query functions. Advantages:

  • expose logical API (facade)
  • State is private. Clients should not know/care about its structure. Nor should it be tempting to return a writeable copy for perf reasons.
  • Perf/scaling - as 1000s of items are added, don't get hotspot in JSON code.

Only constructor functions should be capitalized

This is more of a stylistic convention than an issue with the code itself.

With a few builtin exceptions, Capitalized variable names are conventionally reserved for constructors, which Hover technically isn't. More info: http://eslint.org/docs/rules/new-cap

I'd suggest to either use Hover as a constructor in the documentation (const state = new Hover()) or change it to be lowercase (const state = hover()).

I noticed that it already works either way, but I found the documentation a bit confusing to read because of this ๐Ÿ˜…

State as another class with finder & copy method on it?

Would it hurt to have a function like following, where State is a separate class and have helper functions on it. is this good practice.

var SimpleStore = function(path) {
    function State() {
        this.data = [];
        this.selected = null;
    }

    var p = State.prototype;

    p.copy = function() {
        var ns = new State();
        ns.data = this.data;
        ns.selected = this.selected;

        return ns;
    }

    p.findById: function(id) {
        return _.find(this.data, function(item) {
            return item.id === id;
        });
    }

    var store = Hoverboard({
        init: function(state) {
            GET(path, function(data) {
                store.data(data);
            });

            return new State();
        },

        data: function(state, data) {
            var ns = state.copy();
            ns.data = data;

            return ns;
        },

        select: function(state, selected) {
            var ns = state.copy();
            ns.selected = selected;

            return ns;

        }
    });

    return store;
}

Support module pattern

Creating "classes" like this is possible with Hoverboard already:

var store = Hoverboard(function(){
    this.onSomething = function(){
        console.log('woo');
    }
});
store.something();

but the module syntax below is not yet possible, and it should be:

var store = Hoverboard(function(){
    return {
        onSomething: function(){
            console.log('boo');
        }
    };
});

store.something();

Forgetting to return shouldn't wipe state

I get tripped up forgetting to add return state to the end of my action handlers, and it's a horrible and hard to figure out issue when suddenly part or all of your state is missing.

I propose simply checking if typeof state === 'undefined' and not changing the state if it is undefined. To wipe the state, one can return null or return {} or something.

Any opinions? Concerns?

Hoverboard 3 Roadmap

Here are some thoughts on the next breaking changes. Mostly I want to remove magic, improve predictability and hopefully stabilize the API for the long term.

  • Stop merging state returned from actions.

    I'm starting to regret adding magic around plain objects in Hoverboard. Magic is unpredictable, and I've even surprised myself in how it works. For example, I mistakenly thought returning nothing from an action would leave the state unmodified, but actually it wipes it out. Is this a bug or expected behaviour? Since the answer is unclear, I think the functionality needs to change to be more predictable. Although it'd take a bit more work to merge new properties into state in action handlers, at least the result will be predictable - that whatever is returned from an action handler becomes the new state.

  • Stop making copies of plain objects for state listeners.

    Although this seems like it'd help enforce immutable data, since it only works on the shallow level, it probably gives a false sense of security. It may also cause performance and memory issues when used with a large number of subscribers and large data over frequent updates. With Hoverboard.compose, you should be able to wrap a store in some protection eg. use Object.freeze.

Composition

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:

  • You should be able to pass in an array of stores, an object of stores, a single store (which would just be proxied through), or a static value, and have it set the state permanently of the store.
  • Only stores that are the immediate members of an object should be subscribed to. But you should be able to use Hoverboard.compose inside your object to do nested structures, eg.:
var gameStore = Hoverboard.compose({
    score: scoreStore,
    character: Hoverboard.compose({
        health: healthStore
    })
});
  • You should also be able to pass in arbitrary functions and basically receive a setState function to use with promises or callbacks for async purposes:
var asyncStore = Hoverboard.compose({
    user: function (setState) {
        loadUserFromServer(function (error, user) {
            setState(user);
        });
    }
});

React Native

This module does not work with react native, triggers Unexpected end of script. I have no idea what's causing it tho :(

Complex Async Scenarios..

Hi,

Apologies for so many tickets.

This one is kind of tricky to explain.

Suppose I have an AppStore which has state { isInitted: boolean } and an "init" action.

For app store to be considered initted it must tell the AuthStore to init itself (an async operation) if that operation completes then the UserStore needs to load (async) the user based on the token that the AuthStore loaded from localStorage.

So you have a chain of async operations going on there. Normally you could handle this with promises or async await no problem but with stores its different as they dont return promises.

How would you recommend solving this chained async problem?

Mike

Eliminate dependencies on Flux Dispatcher and EventEmitter?

Hoverboard currently has two dependencies, and we could move to eliminating both of them without affecting the functionality. Hoverboard is already only 4kb minified & gzipped, but eliminating these dependencies could bring it closer to 1kb or less!

  1. EventEmitter - being used as a PubSub for state changes, but nothing more. We could simply use an array of functions per store, and provide an unsubscribe function. This functionality is pretty basic.

  2. Flux Dispatcher - being used to dispatch actions to stores. We don't use more than this from Dispatcher, we only need to call a specific action handler per action, so we don't even need PubSub here. We could easily enforce the rule that actions cannot be called from action handlers, which is probably the only feature in Dispatcher we're taking advantage of.

Any thoughts on this?

Composing actions

After using Hoverboard.compose for a while, I've tried different ways of composing the actions. Hoverboard.compose only composes state data, and actions are ignored here. I tend to compose the actions and the state separately, eg.:

const clickCounter = Hoverboard({
    add: (state=0, num) => state + num
})

export const model = Hoverboard.compose({
    clickCounter
})

export const actions = {
    addClick: clickCounter.add
}

This works, I guess. But it'd be nice to have the actions automatically available on model, instead of having to pass them along. Maybe something like:

model.clickCounter.add(1)

One simple way to approach this, would be to make the stores (and other functions?) available as properties on the composed store. So that would also allow this:

const currentCount = model.clickCounter.getState()

model.clickCounter.getState(clickCount => console.log('there have been', clickCount, 'clicks'))

And if using Hoverboard.compose to map a store's state, the original store's actions could be passed through as well, eg.

const exaggeratedClicks = Hoverboard.compose(clickCounter, clicks => clicks * 2)

exaggeratedClicks.add(5)

const clicks = exaggeratedClicks() // returns 10

And then, doing nested composition would provide a nested structure of actions, eg.

const model = Hoverboard.compose({
    clicks: Hoverboard.compose({
        clickCounter,
        exaggeratedClicks
    })
})

// passed through to original clickCounter.add() action
model.clicks.exaggeratedClicks.add(3)

model.getState() // returns {"clicks": {"clickCounter": 3, "exaggeratedClicks": 6}}

I think that'd be useful without breaking anything. The actions would map nicely to the structure of the data, by default, and you could still choose to create your own object of action mappings yourself.

mixins for React

Any plans to add mixins for React?

Adding these are quite painful.

componentDidMount() {
    this._disposeTodoSubscription = TodoStore.getState(state => this.setState({ todos: state.todos }));
},
componentWillUnmount() {
    this._disposeTodoSubscription();
}

Reflux currently has

mixins: [Reflux.connect(todoStore, "todos")]

and Reflux.ListenerMixin

mixins: [Reflux.ListenerMixin)],
onTodoChange(state) {
    this.setState({ todos: state.todos });
},
componentDidMount() {
    this.listenTo(todoStore, this.onTodoChange);
}

Preserve arguments.length in action calls

I tried to be clever with trying to avoid apply, but just realised that arguments.length on an action handler will always be six when there are six or fewer arguments. Need to fix that.

setState is not a function?

Hey there,

Example

I attempted to run the example in the README, with Hoverboard loaded through WebPack and placed in the window context. As you can see, it doesn't seem to work. I'm running Hoverboard 1.5.0, which seems to be the latest?

Any ideas what's going on here?

Cheers

Debugging tool

In many Flux articles, people seem to love the fact you can drop a console.log into the Dispatcher to see everything that's happening. Since the dispatching in Hoverboard is done internally, maybe it'd be worth adding a hook to add global listeners for all actions and state changes.

Only problem is, Hoverboard has no notion of the name of a state. It only knows the name of the methods being called, the internal store instance, and the store action object. So this might be a possible implementation:

var ValueStore = Hoverboard({
    onSomeAction: function(val) {
        this.setState({ val: val });
    }
});

// something like this would be necessary if you want to log the "name" of the store
ValueStore.name = 'ValueStore';

Hoverboard.onAction(function (store, action, args) {
    console.log('[action]', store.name + '.' + action, args);
});

Hoverboard.onState(function (store, state) {
    console.log('[state]', store.name, state);
});

// outputs the following to console:
// [action] ValueStore.someAction [123]
// [state] ValueStore {"val":123}
ValueStore.someAction(123);

I'm not settled on this API, just wanted to let this simmer here for a while before implementing, in case anyone has additional thoughts.

Maybe it's not even necessary, there are many other ways to debug apps. In a project I have with Hoverboard and React, I have a single render method with a console.log to see all the state entering my views, and that's been good enough so far.

Hoverboard v2 Roadmap

After a few months in use I have some ideas of the next version of the API. Here are some of my goals and thoughts, not set in stone. Please, let me know your thoughts on any of this.

  • Allow Hoverboard to be used as a base class (extended) with ECMAScript 6 classes.
  • Stop being clever about renaming methods (action vs. onAction) and instead consider all public methods as actions.
  • Allow passing initial state into constructor for "rehydration" of models created on server side.
  • Eliminate getInitialState and just use a constructor, rehydration, or override getState instead.
  • Consider alternative names for this.state, getState and setState, but probably will keep them.
  • Figure out what all of this looks like on ES5 as well.
  • If too much friction or confusion, I might just launch this as a new, separate project with a different name.

Here's a possible example of usage in v2:

class MyModel extends Hoverboard {
    constructor(state) {
        super(state);

        // start loading async data immediately
        setTimeout(() => this.addItem('async'), 1000);
    }

    setState(data) {
        // do processing on any data passed through here (eg. add meta data, filter)
        data.listLength = data.list ? list.length : 0;

        return super.setState(data);
    }

    getState(callback) {
        // could start lazily loading async data here
        api.get('list/pages/count').then((pageCount) => this.setState({ pageCount });

        return super.getState(callback);
    }

    // note this is an action and it's not called onAddItem
    addItem(item) {
        var list = this.state.list || [];
        list.push(item);

        this.setState({ list: list });
    }
}

// create instance of model and pass in initial state
var model = new MyModel({ list: ['one', 'two'] });

// add a listener
var unsubscribe = model.getState(function (state) {
    // do stuff with state
});

// call an action
model.addItem('new item');

Protect against circular dependencies in state change listeners

Hoverboard eliminates the need for waitFor by insisting that stores wait for each other via the getState mechanism. However, there is nothing in place to prevent a circular dependency from forming, where Store A listens to the state of Store B, and Store B listens to the state of Store A, which can cause an endless loop. Heck, even Store A could listen to the state of Store A and cause a loop within itself.

So, we need to throw an error if the state changes within a store instance while a state change event from that store is in the middle of being emitted. In other words, only one state change event can go out per store at a time.

We already have a similar protection that an action cannot be called while an action is in progress, so this can be implemented very similarly.

Remove global state

Hoverboard now only has a single private global variable:

// ensure only one action is handled at a time globally
var isActionBeingHandled = 0;

I'd like to eliminate this and I think it's safe to do so. It's there to prevent actions from firing actions to other stores. I only see this as being a problem if the actions become circular (action X in store A calls action Y on store B which then calls action X on store A etc.). So, I think we can move it into each store - so that a single store can't trigger its own actions while an action is being handled, but we can allow it to call actions on other stores.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.