piotrwitek / typesafe-actions

Typesafe utilities for "action-creators" in Redux / Flux Architecture
https://codesandbox.io/s/github/piotrwitek/typesafe-actions/tree/master/codesandbox
MIT License
2.41k stars 98 forks source link

Suggestion: ability to create "routines" #21

Closed duro closed 6 years ago

duro commented 6 years ago

First off, killer work on this library. And I only see it getting better and easier to use with TypeScript 2.8 on the horizon.

I've been trying to get this integrated into a new project we are starting that will have LOTS of async calls to a server. This means we have lots of actions that go through the trigger -> request -> success/fail flow.

Obviously this becomes boilerplate overload if I have to create 4 actions for every one of these. That said, I do need the ability to at the very least describe the action shape of the trigger and success actions.

I came across this library https://github.com/afitiskin/redux-saga-routines which is using redux-actions that makes creating a "routine" that goes through these steps much less boilerplate. That said, it's type safety is nowhere near the ease and cleanliness that typesafe-actions provides.

I've been taking a stab at merging the two concepts together, but I'm still a bit noobish around TypeScript, so making very slow progress.

Since in a sense you have already done the hard work of re-writing most of redux-actions I thought I might throw this out as a suggestion for a feature to add to this library.

If it's outside the scope, I totally get that, but perhaps a companion lib would make sense.

I'm happy to help, but leveraging some of your existing deep knowledge of the TypeScript world would go a long way.

I know this is not a problem unique to my use case, and I know many would benefit from a low boilerplate way to create a type safe action (or dynamically created set of actions) that describe the typical async flow.

Thoughts?

duro commented 6 years ago

BTW, I was thinking of something like this as far as API:

const myRoutine = createRoutine(
  'GET_THINGS_BY_LOCATION_ID',
  {
    trigger: (locationId: string) => ({
      payload: { locationId }
    }),
    success?: (things: Thing[]) => ({
      payload: { things }
    }),
    error?: ...,
  }
)

Much like how redux-saga-routines works this would turn myRoutine into an object that has properties like:

myRoutine.trigger(123)
myRoutine.success([Thing, Thing, Thing])
piotrwitek commented 6 years ago

Hello @duro, New API will have exactly that, I'm finishing working implementation for TS 2.8 RC

Please let me know if this would be enough for your usage (Feedback welcomed!)

// here you'll configure aliases for api e.g. request/success/error or fetch/success/failure or whatever you'll want
const buildAction = new ActionBuilder({ request: 'request', ... })

// one-liner to handle all 3 states of async action (accept 3 generic type arguments for payload type)!!!
const fetchUsers = buildAction('LIST_USERS').async<void, User[], string>();

// Action type "types" are generated dynamically using intersection type, but the string for debugging will be concatenated 'LIST_USERS_REQUEST'
const fetchUsersRequestType: { type: 'LIST_USERS' & 'REQUEST' } = fetchUsers.request();
const fetchUsersSuccessType: { type: 'LIST_USERS' & 'SUCCESS', payload: User[] } = fetchUsers.success([{}]);
const fetchUsersErrorType: { type: 'LIST_USERS' & 'ERROR', payload: string } = fetchUsers.error('Error');

// getType works for switch case in reducers
const type: 'LIST_USERS' & 'ERROR' = getType(fetchUsers.error);

All is type-safe and tight as hell, but you're app will be heaven with no bugs!

duro commented 6 years ago

@piotrwitek This is awesome! Exactly what we were looking for. Let me know if there is any way I can help you get this over the line sooner.

One last thing that was pretty rad about redux-saga-routines was that is had a promiseify function and corresponding middleware so you could wrap the async flow in a promise for interaction with libraries like redux-form or when you need to delegate some kind of final activity to the view layer for when the flow completes/fails.

I have found in my experience that this is especially useful for error handling. I have found that storing errors in the store is actually not very useful in many cases, and requires unnecessary state properties and cleanup.

Looking at what the dev of redux-saga-routines was doing, is he basically implemented a middleware that added some promise resolve and reject props to the meta of the action.

Again, if there is any way to share what you are working on, and I can help with this or anything else, let me know.

piotrwitek commented 6 years ago

@duro thanks for suggestions! I recognize and agree with what you said about the issues with common error state properties, and the need to handle final activities for async operations, I also checked the redux-sage-routines promisifyRoutine solution.

Still I think that the middleware approach should not be a part of this library (for now at least) as there are different solutions for this kind of problem, and such functionality can be easily provided by external library of user preference.

Thanks a lot for support and I'll ping you when the final draft of a new API is published (should be soon), as I would need feedback before releasing to public.

piotrwitek commented 6 years ago

Resolved with new API by recent npm release, please use v2.0.0-rc.0