Reputation: 596
I have the following types:
interface CellsReducer {
source: number;
destination: number;
plan: string;
duration: number;
test: []
}
interface BarReducer {
baz: string;
}
interface AppState {
cells: CellsReducer;
bar: BarReducer;
}
I want to write an interface with the following objects:
interface Props {
store: keyof AppState;
field: // AppState[store]
data: // AppState[store][field]
}
Using Generics didn't get me anywhere. fields
ends up with type never
in the following example:
type Stores<T> = keyof T;
type Fields<T> = keyof T[Stores<T>];
type Props<TState> = {
state: Stores<TState>;
field: Fields<TState>
}
Is there a way of doing this?
Upvotes: 2
Views: 51
Reputation: 249606
You need distinct type parameters for each property in the path. This allows the compiler to reason about the specific fields you specify:
type Props<TState, KStore extends keyof TState, KField extends keyof TState[KStore]> = {
state: KStore;
field: KField
data: TState[KStore][KField]
}
let p: Props<AppState, "cells", "duration"> = {
state: "cells",
field: "duration",
data: 1
}
The reason you get never is because when the compiler tries to expand AppState[keyof AppState]
it will get a union CellsReducer | BarReducer
. Since only common members of union are accesible keyof (CellsReducer | BarReducer)
is never
(no keys are accessible).
The extra parameters capture the actual field, so if KStore
is the string literal type "cells"
keyof AppState["cells"]
will be the keys of that particular field in app state. KField
works similarly allowing us to correctly type data
.
To avoid specifying the state
and field
values twice you can write a helper function:
function propertyFactory<TState>() {
return function <KStore extends keyof TState, KField extends keyof TState[KStore]>(o: Props<TState, KStore, KField>) {
return o;
}
}
let p = propertyFactory<AppState>()({
state: "cells",
field: "duration",
data: 1
})
Upvotes: 1
Reputation: 25790
Do you mean:
interface Props<T, K extends keyof T, V extends keyof T[K]> {
state: keyof T;
field: T[K];
data: T[K][V]
}
Usage:
const props: Props<AppState, 'cells', 'plan'> = { /* ... */ } ;
const props: Props<AppState, 'bar', 'baz'> = { /* ... */ } ;
Upvotes: 1