joshburgess / redux-most

Most.js based middleware for Redux. Handle async actions with monadic streams & reactive programming.
https://www.npmjs.com/package/redux-most
MIT License
139 stars 14 forks source link

withState hoc #33

Open kanitsharma opened 5 years ago

kanitsharma commented 5 years ago

Here is an example of an epic with withState and mergeState

export default withState((s$, a$) => 
  compose(
    map(() => actionSpreader("START")),
    tap(console.log), // [ state, action ]
    mergeState(s$),
    select("FETCH_INIT")
  )(a$)
);

The inner function in withState is automatically uncurried so we can also it as

export default withState(s$ => 
  compose(
    map(() => actionSpreader("START")),
    tap(console.log), // [ state, action ]
    mergeState(s$),
    select("FETCH_INIT")
  )
);

Also, lets discuss if you want the function names to be changed

joshburgess commented 5 years ago

Hmm, I'm not sure about the function names. I'll need to think about this a bit and get back to you.

Also, I was thinking that if this library went down this route of specifying what args should be included through higher order functions that it would also do it for other things, like getState, dispatch, possibly dependencies (like an API Client, etc.)

kanitsharma commented 5 years ago

Hmm, I'm not sure about the function names. I'll need to think about this a bit and get back to you.

Yeah I probably thought so :smile:

Also, I was thinking that if this library went down this route of specifying what args should be included through higher order functions that it would also do it for other things, like getState, dispatch, possibly dependencies (like an API Client, etc.)

hmm as for the getstate and dispatch, if the user is already using the not stricter API then he would get the getstate and dispatch object instead of state$.

For the dependencies we can use an approach similiar to redux-observables, passing the dependencies in the createEpicMiddleware and receiving it as third argument with the use of HOC

and since our HOC is not only providing the state$, so we need to name it accordingly, I will leave that to you :smile:

joshburgess commented 5 years ago

@kanitsharma Well, the idea was to change to just having a single API that everyone uses + using the higher order functions in an ad hoc sort of way to add functionality, where you only import and use them when needed. This would simplify the library in that everyone who uses it would work in basically the same way by default.

So, the two APIs would go away and instead we'd just have one API that by default takes in only an action stream. Then, for people who want access to the quick-and-dirty/escape-hatch-y getState/dispatch, they could pull in something like withStore or withMiddlewareApi or something that injects an object like { getState, dispatch } and then they'd have access to that.

Then, the same thing would be available for the state stream, like what you've shown here.

And there could possibly be other useful things as well. For example, we could allow the user to define dependencies upon middleware construction, like what you described above, but instead of ALWAYS injecting those dependencies we just provide access to them through a higher order function, like withDependencies.

I'm not sure whether or not this is the best route to go down, but that was the original idea. It has pros and cons.

Pros:

Cons:

I'm not 100% sure what the best solution is off the top of my head, but I do think changing the library so that there is only one base API + optional flexibility is a good idea. Finding the right solution will probably require some experimenting with different ideas.

joshburgess commented 5 years ago

Another, simpler, option would be to just skip the HOFs altogether and just always pass everything through:

epicArgs => ...

And then the user could use destructuring to pluck off the things they want:

({ dispatch, getState, dependencies, stateStream }) => ...

This is definitely the simplest option, but maybe not the best? Not sure.

I don't want to remove access to dispatch and getState altogether, but I would like to discourage use of them somehow, since they should be viewed as escape hatches rather than the default way to go about things, because you really don't need them.

kanitsharma commented 5 years ago

I am all up for having different HOFs for different use cases, As I see it would be very exciting to compose the HOFs and make it all pointfree :smile:

joshburgess commented 5 years ago

I'm also a fan of pointfree style, but there is a caveat, unfortunately. Pointfree style in TypeScript/Flow can be pretty annoying due to TS/Flow not being able to infer from right to left with compose. It requires a lot of extra type annotating that wouldn't be required in languages like Haskell, etc. :(

BUT, I do think it would be nice to support a pointfree style for those who are willing to annotate or people who are just writing in plain JS.

joshburgess commented 5 years ago

Side-note: I would like to rewrite in TypeScript due to getting more into it recently at work, etc. + the library already having TS type definitions, but this would probably also be a good time to add Flow types to the library. I think @most/core already has both TS & Flow types available.

kanitsharma commented 5 years ago

I'm also a fan of pointfree style, but there is a caveat, unfortunately. Pointfree style in TypeScript/Flow can be pretty annoying due to TS/Flow not being able to infer from right to left with compose. It requires a lot of extra type annotating that wouldn't be required in languages like Haskell, etc. :(

Agree, but since @most/core doesn't provide the fluent style/chaining most of the users will have to use the composition + pointfree style so the HOFs pattern makes sense.

kanitsharma commented 5 years ago

({ dispatch, getState, dependencies, stateStream }) => ...

and as far as this is concerned we can use the withall HOF that you talked about to achieve this kind of mechanism.

kanitsharma commented 5 years ago

Update on this, I was a little busy last week so didnt get a chance to work on it. Gonna work on the HOFs this week :muscle: