vigetlabs / microcosm

Flux with actions at center stage. Write optimistic updates, cancel requests, and track changes with ease.
http://code.viget.com/microcosm/
MIT License
487 stars 29 forks source link

Spec for Actions in 13.x #477

Closed nhunzaker closed 6 years ago

nhunzaker commented 6 years ago

Microcosm is now powered by observables. Along the way, I've come to the conclusion that there are a few design course corrections that need o be made:

  1. Consistent action payload shapes across lifecycle methods
  2. Action meta data
  3. Renaming action statuses to match observables
  4. Removing subscription to open and cancel
  5. Actions are behavior subjects (TODO: Write about this)

Consistent action payload shapes

I think the biggest change is the way that data interacts with the action lifecycle. In 12.x, Microcosm's action states usually end up exposing are very discrete. Let's say we want to fetch a list of blog posts:

repo.push(getPosts, { page: 1 })

Each of these states has a different shape (though error kind of gets a pass). This is unfortunate for sharing callbacks across multiple statuses and for intuiting the shape of an action's payload within domains.

I think the payload of an action should be consistent as it is correctly processing:

Error still gets a pass here. Long term, I think it probably makes sense to expose a consistent set of error types, possibly based on status codes, but that's for another time...

Action meta data

But how do I get the query parameters or indicate request progress? Great question. Meta data! I propose the second argument of action update methods allow the assignment of meta data:

action.open([], { page: 1 }) // meta: { page: 1 }
action.update([], { progress: 0.5 }) // { page: 1, progress: 0.5 }
action.update([], { progress: 0.9 }) // { page: 1, progress: 0.9 }
action.done([], { progress: 1 }) // { page: 1, progress: 1 }

TODO: I'm not sure I love this additive approach. I don't like that it is easy to forget to update progress for the done state.

This gets exposed to domains as the third argument:

class PostsDomain {
  handleUpdate(state, data, meta) {
    console.log(meta) // { progress: 0.5 } 
  }
  register() {
    return {
      [action.update]: this.handleUpdate
    }
  }
}

When meta data is provided, given object is merged into an existing hash. Or, we could consider a separate method like action.meta.

Renaming action statuses to match observables

These lifecycle methods are:

For backwards compatibility, it probably makes sense to alias these.

Breaking: I want to remove start and cancel, though actions will still be cancelable.

Removing subscription to open and cancel

In just about every circumstance I've encountered, the relationship between open and update is problematic. I don't like how open and update compete for loading states, and how open never has a usable payload.

I've never subscribed to cancel and I'm not sure it is a good idea.