I am working on a scenario of handling an array type property with indexed access. Tried to encapsulate the problem in simplest form here.
type User = {
readonly name: string;
readonly roles: ReadonlyArray<string>;
}
const buildActions = <T extends User>(u: T) => {
// Implementation
return {login: (role: T['roles'][number]) => {
console.log(u, role);
}}
}
const actions = buildActions({name: 'user', roles: ['admin', 'client']} as const);
actions.login('client');
So above code works fine. But in my scenario the property roles is optional.
type User = {
readonly name: string;
readonly roles?: ReadonlyArray<string>;
}
In above case we get error
Type 'number' cannot be used to index type 'T["roles"]'
I tried to use conditional type to handle the scenario but didn't worked out.
const buildActions = <T extends User>(u: T) => {
// Implementation
return {login: (role: T['roles'] extends never ? never : T['roles'][number]) => {
console.log(u, role);
}}
}
Definitely it's not the right way to handle such scenario, but covers the use case very well.
Any idea how to get through this typing scenario?
Thanks
Using the conditional types solved the problem.
type User = {
readonly name: string;
readonly roles?: ReadonlyArray<string>;
}
const buildActions = <T extends User>(u: T) => {
// Implementation
return {login: (role: T['roles'] extends string[] ? T['roles'][number] : never) => {
console.log(u, role);
}}
}
const actions = buildActions({name: 'user', roles: ['admin', 'client']} as const);
actions.login('client');
Tried that earlier but seems there is a precedence issues with TS extends keyword. Considering that T['roles'] as type string[] | undefined The following code below works fine:
T['roles'] extends string[] ? T['roles'][number] : never
But if we reverse the condition it fails.
T['roles'] extends undefined ? never : T['roles'][number]
So never use conditional types with undefined assertion.