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

Infer action and return type from action creator #243

Closed jaulz closed 3 years ago

jaulz commented 4 years ago

First of all, thanks a lot for this library as it makes my code base so much safer 😊 However, I am facing a special issue right now which is quite tricky and might also be related to Typescript itself and not this library.

The example below is a simplified example what I would like to achieve. The function getTypeAndPayload should be able to get the type of the incoming action creator but also the return type of it.

const sampleActionCreator = createAction(
  'TEST',
  (test: boolean) => ({
    test,
  }),
  (test: boolean) => ({
    test,
  })
)()

function getTypeAndPayload<
  TType extends TypeConstant
>(actionCreator: ActionCreator<TType> & ActionCreatorTypeMetadata<TType>) {
  return {
    type: getType(actionCreator),
    getSource: (source: ReturnType<typeof actionCreator>) => source,
  }
}

function getTypeAndPayloadWithGeneric<
  TType extends TypeConstant,
  TActionCreator extends ActionCreator<TType> & ActionCreatorTypeMetadata<TType>
>(actionCreator: TActionCreator) {
  return {
    type: getType(actionCreator),
    getSource: (source: ReturnType<typeof actionCreator>) => source,
  }
}

function getTypeAndPayloadWithTwoParameters<
  TType extends TypeConstant,
  TActionCreator extends ActionCreator<TType> & ActionCreatorTypeMetadata<TType>
>(
  actionCreator: ActionCreator<TType> & ActionCreatorTypeMetadata<TType>,
  actionCreator2: TActionCreator
) {
  return {
    type: getType(actionCreator),
    getSource: (source: ReturnType<typeof actionCreator2>) => source,
  }
}

The function modules return different types:

/*
{
    type: "TEST";
    getSource: (source: Action<"TEST">) => Action<"TEST">;
}
*/
const test1 = getTypeAndPayload(sampleActionCreator)

/*
{
    type: string;
    getSource: (source: PayloadMetaAction<"TEST", {
        test: boolean;
    }, {
        test: boolean;
    }>) => PayloadMetaAction<"TEST", {
        test: boolean;
    }, {
        test: boolean;
    }>;
}
*/
const test2 = getTypeAndPayloadWithGeneric(sampleActionCreator)

/*
{
    type: "TEST";
    getSource: (source: PayloadMetaAction<"TEST", {
        test: boolean;
    }, {
        test: boolean;
    }>) => PayloadMetaAction<"TEST", {
        test: boolean;
    }, {
        test: boolean;
    }>;
}
*/
const test3 = getTypeAndPayloadWithTwoParameters(
  sampleActionCreator,
  sampleActionCreator
)

The last one is actually the one I am interested in (with a const type and a proper return type). However, I don't want to have two input parameters. Is there any trick to infer both types at the same time?

Here is a playground link: https://stackblitz.com/edit/typescript-jf7alj?devtoolsheight=33&file=index.ts

Thanks a lot in advance!

jaulz commented 3 years ago

Okay, I got it finally... just for anyone who's also interested in it:

function getTypeAndPayloadFinally<
  TType extends TypeConstant,
  TActionCreator extends ActionCreator<TType> & ActionCreatorTypeMetadata<TType>
>(actionCreator: TActionCreator) {
  return {
    type: getType(actionCreator) as ReturnType<typeof actionCreator>['type'],
    getSource: (source: ReturnType<typeof actionCreator>) => source,
  }
}

The important piece is the explicit cast to ReturnType<typeof actionCreator>['type'].