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

Generic Epic that takes in an AsyncAction? #46

Closed pachuka closed 6 years ago

pachuka commented 6 years ago

Hi Piotrwitek,

I'm having some trouble wrapping my head around how to pass an AsyncAction created via CreateAsyncAction while keeping the typing information, for example in my case I am trying to create a generic GraphQL query epic using ApolloClient since all of those will have the same REQUEST/SUCCESS?FAILURE flow:

What type should the asyncAction be so that the isActionOf and the action.Payload resolve properly?

const createAsyncEpic = (asyncAction: <type?>, mutation: any) {
  const epic: Epic<RootAction, RootState> = (action$, state$) =>
      action$.pipe(
        filter(isActionOf(actions.request)),
        withLatestFrom(state$),
        mergeMap(([action, state]) =>
          client
            .mutate({ mutation, variables: { ...action.payload } })
            .then((result: ApolloQueryResult<P2>) => {
              return actions.success(result.data);
            })
            .catch((error: ApolloError) => {
              return actions.failure(error);
            })
        )
      );

    return epic;
};

Thanks!

piotrwitek commented 6 years ago

I would suggest to create a question on stack overflow, you can link it here for reference.

pachuka commented 6 years ago

https://stackoverflow.com/questions/50546844/how-to-pass-actions-created-with-typesafe-actions-createasyncaction-to-a-functio

abdurahmanus commented 5 years ago

I have simillar trouble. Making generic method to deal witch async action (for making api calls in thunks, sagas or epics) could reduce lots of repetetive code.

piotrwitek commented 5 years ago

@abdurahmanus check SO question I have provided some quick answer there, hope that helps.

pachuka commented 5 years ago

@piotrwitek - nice! I have been using something similar, but this is much cleaner now.

piotrwitek commented 5 years ago

Glad you like it 🙂 I also have plans to improve all the remaining Creator types to better handle generic scenarios in consumer code

abdurahmanus commented 5 years ago

Thank you for quick response! This is exactly what I was looking for.

abdurahmanus commented 5 years ago

One more issue however.

const aa = createAsyncAction("request", "success", "failure")<
  number,
  { foo: "bar" },
  string
>();

function* asyncSaga<
  TRequestType extends string,
  TRequestPayload,
  TSuccessType extends string,
  TSuccessPayload,
  TFailureType extends string,
  TFailurePayload
>(
  asyncAction: AsyncActionCreator<
    [TRequestType, TRequestPayload],
    [TSuccessType, TSuccessPayload],
    [TFailureType, TFailurePayload]
  >
) {
  // not implemented
  yield;
};

function* someSaga() {
  yield call(asyncSaga, aa);
}

image

image

piotrwitek commented 5 years ago

Sorry but this is not typesafe-actions related, it's obviously an issue with the call type.

If you can solve this issue I'm willing to add it in the recipes section as it can be really useful for other ppl.

abdurahmanus commented 5 years ago

One workaround for this case is to explicitly specify call type. Something like this. Looks not very ellegant thow.

const aa = createAsyncAction("request", "success", "failure")<
  number,
  { foo: "bar" },
  string
>();

const aa2 = createAsyncAction("request1", "success", "failure")<
  boolean,
  { foo: "bar" },
  string
>();

function* asyncSaga<
  TRequestType extends string,
  TRequestPayload,
  TSuccessType extends string,
  TSuccessPayload,
  TFailureType extends string,
  TFailurePayload
>(
  asyncAction: AsyncActionCreator<
    [TRequestType, TRequestPayload],
    [TSuccessType, TSuccessPayload],
    [TFailureType, TFailurePayload]
  >,
  requestPayload: TRequestPayload
): IterableIterator<any> {
  // not implemented
}

type Actions<AC> = AC extends AsyncActionCreator<infer RA, infer SA, infer FA>
  ? { request: RA; success: SA; failure: FA }
  : never;

type AsyncSaga<AC extends AsyncActionCreator<any, any, any>> = {
  (a: AC, rp: Actions<AC>["request"][1]): any;
};

function* someSaga() {
  asyncSaga(aa, 1);

  yield call<AsyncSaga<typeof aa>>(asyncSaga, aa, 2); // correct actions and request payload

  // yield call<AsyncSaga<typeof aa>>(asyncSaga, aa); // not all arguments provided
  // yield call<AsyncSaga<typeof aa>>(asyncSaga, aa, "2"); // wrong request payload type
  // yield call<AsyncSaga<typeof aa>>(asyncSaga, aa2, 2); // wrong action
}
piotrwitek commented 5 years ago

Isn't it the problem with the return type of asyncSaga that call is not inferring correctly? What is the type declaration for call

abdurahmanus commented 5 years ago

I think no. As you can see from screenshots above. Type "string" is not assignable to type "request" and so on

piotrwitek commented 5 years ago

What you say is actually a symptom, not a cause. The error shows that the inference in the call function didn't work at all falling back to default types.