-
-
Save acdlite/c7eb7ac45617d9332b60 to your computer and use it in GitHub Desktop.
| import Flummox from 'flummox'; | |
| import { Map } from 'immutable'; | |
| // Instead of a constructor, just wrap flux creation inside a function | |
| export default function createFlux() { | |
| const flux = new Flummox(); | |
| // Actions are the same. Just pass an object instead of a class. | |
| const thingActions = flux.createActions('things', { | |
| incrementSomething(amount) { | |
| return amount; | |
| }, | |
| async createThing(newThing) { | |
| // async/await isn't strictly necsesary here -- we could just return the | |
| // the promise, but I like the reminder that this is async | |
| return await WebAPIUtils.createThing(newThing); | |
| } | |
| }); | |
| const thingActionIds = fooActions.getActionIds(); | |
| // Note that everything above is already possible in the current version | |
| // of Flummox. | |
| // | |
| // Here's where things get interesting... | |
| const thingStore = flux.createStore('things', { | |
| getInitialState: () => ({ | |
| things: new Map(), | |
| _pendingThings: new Map(), | |
| counter: 0 | |
| }), | |
| // Instead of `this.register()` et al, use hooks (like React's lifecycle | |
| // hooks) to set up action handlers. `register()` should return a hash of | |
| // action ids mapped to handlers. | |
| register: () => ({ | |
| // Return new store state, given the previous state and the value sent by | |
| // the action. Like with `setState()`, the new state is merged with the | |
| // old state. It's like a transactional state update, or a | |
| // reduce operation (except it's a merge, not a replace). | |
| // | |
| // All params are passed as one hash, to support possible future interop | |
| // with RxJS. Yay for destructuring! | |
| [thingActionIds.incrementSomething]: ({ prevState, value: amount }) => ({ | |
| counter: prevState.counter + amount | |
| }), | |
| // For async actions, use a sub-hash with success and failure handlers. | |
| // The naming convention is taken from RxJS observers. Notice that there's | |
| // no `onBegin`, which you might expect if you're used to | |
| // Flummox's `registerAsync()`. For that, use the `registerOnStart()` hook | |
| [thingActionIds.createThing]: { | |
| onNext: ({ prevState, value: newThing, payload: { dispatchId } }) => ({ | |
| things: prevState.things.merge({ | |
| [newThing.id]: newThing | |
| }), | |
| _pendingThings: prevState.delete(dispatchId) | |
| }), | |
| onError: ({ payload: { dispatchId } }) => ({ | |
| _pendingThings: prevState.delete(dispatchId) | |
| }) | |
| } | |
| }), | |
| // Specify handlers that fire at the beginning async actions, in order to | |
| // perform optimistic updates | |
| registerOnStart: () => ({ | |
| [thingActionIds.createThing]: ({ prevState, payload: { dispatchId, actionArgs: [ newThing ] }}) => ({ | |
| _pendingThings: prevState._pendingThings.merge({ | |
| [dispatchId]: newThing | |
| }) | |
| }) | |
| }) | |
| }); | |
| // We could support both the class-based API and this new functional API | |
| // without much fuss. I think this is important, since much of the appeal of | |
| // Flummox comes from the familiarity and predictability of its API. Many | |
| // people are not used to functional programming concepts, and will want to | |
| // stick with classes. | |
| } |
I think part of the issue is with async actions we have one actionId representing two separate streams (promises, resolved promise results).
I wonder if theres a way we could identify we want the promise stream instead of the resolved values:
{
register: () => ({
[thingActionIds.createThing.begin]: ...,
[begin(thingActionIds.createThing)]: ...,
[thingActionIds.createThing]: { onNext, onError }
})
}I use begin for lack of a better name.
That's a clever idea. Except action ids (constants) are strings, and you can't attach properties to them. But I like the notion of using a special key for the begin stream.
we want the promise stream instead of the resolved values
That's a better description of what I was trying to say above. The begin / start stream is essentially the result of a mapping transformation on the stream of promises, whereas the onNext stream is the result of a flatMap-ing transformation on the stream of promises.
I do understand why you want to separate them out into two sections, but just as for tappleby it feels a bit off for me. His syntax would allow us to at least keep things a bit closer together. How about something like this:
{
register: () => ({
[thingActionIds.createThing]: {
begin() {...},
stream: {onNext, onError}
}
})
}
But now that I've written that out, it feels weird having to nest the stuff like that. I'll leave it here for discussion anyway.
I agree with the combine syntax feeling unintuitive. I see your point with these almost being 2 separate streams but it still feels weird having related logic broken out into different sections.
If we zoom out at a higher level, what advantages does this syntax offer us? does following the observable contract allow for interop with other apis (RxJs) or is it more for a familiar interface?