Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
sto alternate api
// store.js
let {store, handler} = sto(initialState, reduceFn); // where reduceFn: function(currentState, action, ...args){}
dispatcher.register(handler);
export store;
// elsewhere
store.get() // -> current state
store.toObservable() // -> to be used with .observe()
// that's it.
@threepointone

This comment has been minimized.

Copy link
Owner Author

threepointone commented May 5, 2015

one problem I see here is that the stores aren't individually testable unless handler is exported as well.

@threepointone

This comment has been minimized.

Copy link
Owner Author

threepointone commented May 5, 2015

ie- let's say you wanted to test how your userstore reacts to dispatches. without access to the handler, you'd have to initialise a dispatcher and all the other stores (mocked, probably). While this is completely ok, and indeed, is how most flux apps are tested ala jest etc; it's annoying to me :(

@threepointone

This comment has been minimized.

Copy link
Owner Author

threepointone commented May 5, 2015

I suppose someone who wanted to test stores without all the rest could just export handler. Huh, not such a problem after all, as long as the developer also has the discipline to never touch the handler.

@gaearon

This comment has been minimized.

Copy link

gaearon commented May 5, 2015

Let's think about time travel.

I want to support this scenario:

  1. Stores are registered with dispatcher and work as usual
  2. Suddenly dispatcher says “let's roll back to state after action N fired” (Let's assume custom dispatcher)
  3. Can we make stores “tune into” that snapshotted state and have their observables emit it?
  4. Then dispatcher says “let's roll back to state after action M fired”
  5. Again, can we make stores replay that state?
  6. Now can we resume the normal flow?

These are interesting API-wise, because if the store owns the state and chooses to emit it in response to dispatcher's events, it can't “load” an older snapshot. But if it doesn't own the state, this should be easily doable.

Does API need to be tweaked to support this scenario? What would Dispatcher be like?

@threepointone

This comment has been minimized.

Copy link
Owner Author

threepointone commented May 5, 2015

[it's 430 am, so pardon any hippopotamuses I make]

assuming a 'perfect' world, then a rollback is simply a reset + replay of actions till N. actually, to get to any point X -> reset + replay to X.

In the app I'm building at work, to get replay working, I had -

  • a debug store that only logged actions
  • every store had a reset action case handler that would simply load initial state again. this could be made convenient via store.reset() (as long it's used ONLY for this.)
  • actions replay and replayed which did the actual replay (with a 100ms gap per action for a cool demo)

so I could do actions.replay() and watch the whole app cycle through everything that happened. Now if those 3 things were 'automatic', it's be pretty sweet. Shouldn't be hard to get into disto.

(however, this won't be "real" time travel, because you wouldn't see the transitions in reverse :P I assume that's just fine for now)

The dispatcher would have to 'return' something to be used as a snapshot. so you could do -

var moment1 = dispatcher.dispatch(someAction, ...args);
// then do a bunch of other dispatches
var moment2 = dispatcher.dispatch(someOtherAction, ...args);

// and then 

dispatcher.goTo(moment1);
dispatcher.goTo(moment2);

This too shouldn't be too hard to implement, though yeah, it'll take a little bit of internal jiggling on the dispatcher.

@gaearon

This comment has been minimized.

Copy link

gaearon commented May 5, 2015

assuming a 'perfect' world, then a rollback is simply a reset + replay of actions till N. actually, to get to any point X -> reset + replay to X.

Yeah... But. Since we already got that state, why not cache it? (Only in dev, say, for last 500 actions.) It seems to me that one can't achieve a smooth travelling experience if repopulating the stores is O(n) where n is how far.

@threepointone

This comment has been minimized.

Copy link
Owner Author

threepointone commented May 5, 2015

Fair point, just tried with 500 actions and zero delay and it froze hard for a couple of seconds.

But then stores would need a .setState() equivalent? Again, doable if only for time travel and not used for anything else. I'm open to alternate implementations.

Alternately -

  • freeze view renders while replaying and ONLY render the last state. this seems to make it much faster.
  • run the replay on requestAnimationFrame/setInterval (fn, 0)/setImmediate(fn) per action. it's not "instantly" instantaneous, but it looks great (if what you're looking for a is a fast visual replay)
  • maybe run the dispatcher/stores on a web worker while time travelling? too sleepy to think of an implementation right now, but sure has hipster value.

(will get back to this when I wake, but please feel free to keep adding stuff)

@threepointone

This comment has been minimized.

Copy link
Owner Author

threepointone commented May 5, 2015

What are the usecases for timetravel in stores? Debugging, surely, but what else? Undo/redo?

@gaearon

This comment has been minimized.

Copy link

gaearon commented May 6, 2015

But then stores would need a .setState() equivalent?

Why? If they don't own state and only tell how to compute it, maybe “dispatcher” has the power to give components some older state of all stores instead of the current state.

What are the usecases for timetravel in stores?

Debugging, for sure. Now that I'm thinking of it, I also want to support restore-from-JSON and serialize-to-JSON. Which doesn't usually work well with Flux —but— our Stores don't need to own the state. The may just specify calculation. If dispatcher holds state and uses stores to reduce it and “advance” it, it can hold it in a single tree internally and snapshot/restore/rollback to any point.

@threepointone

This comment has been minimized.

Copy link
Owner Author

threepointone commented May 6, 2015

But that's the thing, they do own it. Specifically, they hide it. Like an observable; you can't set the 'current' value to operate on. To get around this you'll need a .setState(), at least exposed to the dispatcher.

Alternately, yes, the dispatcher/view controller could bypass all that and load cached state. But then you can't fire further actions until you get back to where you came from.

@threepointone

This comment has been minimized.

Copy link
Owner Author

threepointone commented May 6, 2015

just read the part where dispatcher holds state. good idea, need to think it though..

@threepointone

This comment has been minimized.

Copy link
Owner Author

threepointone commented May 6, 2015

export const store = dispatcher.register(initialState, reduceFn);

this changes the register signature, but should be easier to do the above now.

@gaearon

This comment has been minimized.

Copy link

gaearon commented May 6, 2015

Here's something I played with: https://gist.github.com/gaearon/c02f3eb38724b64ab812
Let me know your thoughts!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.