Reputation: 505
I am trying to write a helper that let's me subscribe to a specific type of dispatched actions in a React app. These are the action interfaces I'm working with:
enum ActionTypes {
MoveToLine = 'MOVE_TO_LINE',
MoveToColumn = 'MOVE_TO_COLUMN',
}
interface MoveToLineAction {
type: ActionTypes.MoveToLine;
payload: { line: number };
}
interface MoveToColumnAction {
type: ActionTypes.MoveToColumn;
payload: { column: number };
}
type Actions = MoveToLineAction | MoveToColumnAction;
And here's the implementation of subscribeToAction
:
const subscribeToAction = <A extends { type: string }, T extends ActionTypes>(
type: T,
onAction: (a: A) => void,
) => (action: A) => {
if (action.type === type) {
onAction(action);
}
};
I want to be able to use it like this:
// Subscribe to a specific type of action
subscribeToAction(ActionTypes.MoveToLine, action => {
action.payload.line;
});
The problem I'm running into is that I want the type of action
in the onAction
listener to be inferred automatically. In the code above, I get a Property 'payload' does not exist on type '{ type: string; }'
error. I can manually type the listener to get around this like this:
subscribeToAction(ActionTypes.MoveToLine, (action: MoveToLineAction) => {
action.payload.line;
});
But it seems redundant to pass in an ActionType
and then also have to specify the action type in the listener. Do you guys have any suggestions as to how to avoid doing this?
Upvotes: 2
Views: 454
Reputation: 328282
Here's how I'd go about it:
type ActionOfType<T extends ActionTypes> = Extract<Actions, { type: T }>;
const subscribeToAction = <T extends ActionTypes>(
type: T,
onAction: (a: ActionOfType<T>) => void,
) => (action: Actions) => {
if (action.type === type) {
// assertion necessary or explicit type guard:
onAction(action as ActionOfType<T>);
}
};
subscribeToAction(ActionTypes.MoveToLine,
action => { // action inferred as MoveToLineAction
action.payload.line;
}
);
You don't really want two generic parameters in subscribeToAction()
. You want T
corresponding to the ActionTypes
constituent passed in as type
, and you want the argument to the onAction
callback to automatically be constrained to the corresponding constituent of Actions
. The ActionOfType<T>
type function shows how you can do this. Oh, and the return value of subscribeToAction
should be a function that takes any Actions
, right? That's why you do the if (action.type === type)
check.
Also note that because action
is of type Actions
, you need to tell the compiler that the test if (action.type === type)
actually narrows action
down to ActionOfType<T>
. I just use a type assertion.
Okay, hope that helps you. Good luck!
Upvotes: 2