1
export type MakeActionType = (name: string) => {[key: string]: string}
const combineActionType = <AT extends (MakeActionType | undefined)[]>(...actionTypeList: AT) => (name: string) => {
    return actionTypeList.reduce(
        (previous, makeActionType) => {
            const actionType = makeActionType ? makeActionType(name) : {}
            return {
                ...previous,
                ...actionType,
            }
        },
        {}
    )
}

Now I applied the function above like this:

const abAction = combineActionType((name) => ({a: 'a'}), (name) => ({b: 'b'}))
const ab = abAction('ab')

I would like ab to contain a and b properties, but ab returns {} type, that is why ab.a or ab.b are not working.

ab.a //err
ab.b //err

How can I define a type of ab that contains both 'a' and 'b' properties?

Tom Le
  • 117
  • 10

1 Answers1

1

You need to explicitly tell typescript what the return type of reduce will be, typescript will not be able to infer the type.

The return type should be an intersection of all the function return types. We can get a union of the function types using a type query (AT[number]) and we can get the return type using the ReturnType conditional type. To transform the union of return types to an intersection we can use the UnionToIntersection type from here (don't forget to upvote jcalz)

type UnionToIntersection<U> = 
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
export type MakeActionType = (name: string) => { [key: string]: string }
const combineActionType = <AT extends (MakeActionType | undefined)[]>(...actionTypeList: AT) => (name: string)  => {
    return actionTypeList.reduce(
        (previous, makeActionType) => {
            const actionType = makeActionType ? makeActionType(name) : {}
            return {
                ...previous,
                ...actionType,
            }
        },
        {} 
    )  as UnionToIntersection<ReturnType<Exclude<AT[number], undefined>>>
}


const abAction = combineActionType((name) => ({a: 'a'}), (name) => ({b: 'b'}))
const ab = abAction ('a') // { a: string } & { b: string }

Playground link

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357