Imagine the following simplified setup:
import { Action, AnyAction } from 'redux'; // interface Action<Type> { type: Type } and type AnyAction = Action<any>
export type FilterActionByType<
A extends AnyAction,
ActionType extends string
> = A['type'] extends ActionType ? A : never;
type ActionUnion = Action<'count/get'> | Action<'count/set'>;
type CountGetAction = FilterActionByType<ActionUnion, 'count/get'>;
// expected: Action<'count/get'>
// received: never
Is there a way to accomplish this? (typescript 3.7 is an option)
You want FilterActionByType<A, T>
to take a union type A
and act on each member separately, and then take all the results and unite them in a new union... that means you want FilterActionByType<A, T>
to distribute over unions (at least in A
). You can use distributive conditional types to do this, by making sure that your conditional type is of the form type FilterActionByType<A, T> = A extends ...
. Having the bare A
as the checked type triggers the distribution you want.
But: your conditional type is of the form type FilterActionByType<A, T> = A["type"] extends ...
, in which A
is "clothed" by the property lookup, and so is not distributive. That means the A["type"] extends ActionType
takes the whole union value for A
, namely (in your case) ActionUnion
. And ActionUnion["type"] extends "count/get"
becomes ("count/get" | "count/set") extends "count/get"
, which is false. (X extends (X | Y)
is always true, but (X | Y) extends X
is not true in general.) So you get never
.
The simplest way to change what you have into a distributive conditional type is just to wrap your definition in A extends any ? ... : never
:
export type FilterActionByType<
A extends AnyAction,
ActionType extends string
> = A extends any ? A['type'] extends ActionType ? A : never : never;
type ActionUnion = Action<'count/get'> | Action<'count/set'>;
type CountGetAction = FilterActionByType<ActionUnion, 'count/get'>;
// type CountGetAction = Action<"count/get">
Or you could refactor your original conditional type to be distributive without wrapping it:
export type FilterActionByType<
A extends AnyAction,
ActionType extends string
> = A extends { type: ActionType } ? A : never;
type CountGetAction = FilterActionByType<ActionUnion, 'count/get'>;
// type CountGetAction = Action<"count/get">
The check A extends {type: ActionType}
is the distributive version of A["type"] extends ActionType
.
Either way should work for you, but the latter is probably cleaner.
Okay, hope that helps; good luck!
Link to code
The best solution from Piotr Lewandowski :
type FilterFlags<Base, Condition> = {
[Key in keyof Base]:
Base[Key] extends Condition ? Key : never
};
type AllowedNames<Base, Condition> =
FilterFlags<Base, Condition>[keyof Base];
type SubType<Base, Condition> =
Pick<Base, AllowedNames<Base, Condition>>;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With