Currently, I am in the process of developing a function to generate Redux actions (similar to createAction
from redux toolkit). My goal is to create a function that can produce action generators based on the types provided to the creator function.
const createGenericAction = <T extends string>(type: T) => <
A extends {},
B extends {}
>(
payloadGenerator: (a: A) => B
) => {
const factory = (payload: A) => ({
type,
payload: payloadGenerator(payload),
});
factory.toString = (() => type) as () => T;
return factory;
};
This is how the creator function currently appears, including the toString
implementation for compatibility with redux-toolkit.
It works well when the payloadGenerator
is not generic, like so:
const someAction = createGenericAction('someAction')(
(payload: { a: number; b: string }) => payload
);
The correct type is inferred in this scenario.
However, issues arise when the payloadGenerator
is generic, causing the entire type inference process to break down:
const someAction = createGenericAction('someAction')(
<T extends string>(payload: { value: T }) => payload
);
Argument of type '<T extends string>(payload: { value: T; }) => { value: T; }' is not assignable to parameter of type '(a: {}) => { value: string; }'.
Types of parameters 'payload' and 'a' are incompatible.
Property 'value' is missing in type '{}' but required in type '{ value: string; }'.ts(2345)
A more complex example
enum Element {
Elem1 = 'elem1',
Elem2 = 'elem2',
}
type ElementValueMapper = {
[Element.Elem1]: string;
[Element.Elem2]: number;
};
const someAction = createGenericAction('someAction')(
<T extends Element>(payload: { e: T; value: ElementValueMapper[T] }) =>
payload
);
With this action, calls such as the following will be permitted:
someAction({ e: Element.Elem1, value: 'string' }); // okay
someAction({ e: Element.Elem2, value: 5 }); // okay
However, calls like the one below will be rejected:
someAction({ e: Element.Elem1, value: 5 }); // error: value should be of type string