Home > front end >  Inferring the correct type with Generics
Inferring the correct type with Generics

Time:08-25

I'm trying to create a reducer function that allows us to infer the allowed properties and value in a payload, and this currently works, if the type property in the dispatch is changed, it will allow for only the values that are associated with the ToolMap to be passed and will throw errors if wrong name value is passed in payload.

The issue occurs in the receiving end of the reducer, the action.payload can't properly infer that the name property is correct, it always thinks it a union type of either CursorToolName or DrawingToolName. Which makes me a bit confused as when the payload reaches that case in the switch statement it should always be the correct type.

type ToolCategory = 'CursorTools' | 'DrawingTools'
type CursorToolName = 'Cursor' | 'Measure'
type DrawingToolName = 'Brush' | 'Pencil'


type ToolMap = {
    CursorTools: CursorToolName;
    DrawingTools: DrawingToolName
}

interface ToolState {
    CursorTools: Tool<'CursorTools'>
    DrawingTools: Tool<'DrawingTools'>
}

interface ToolAction<T extends ToolCategory>{
    type: T;
    payload: Tool<T>
}

interface Tool<T extends ToolCategory> {
    name: ToolMap[T];
    onEnter?: () => void;
}

const reducer = <T extends ToolCategory>(
    state: ToolState,
    action: ToolAction<T>
): ToolState => {
    switch(action.type){
        case 'CursorTools': {
            return {
                ...state,
                CursorTools: {
                    // This doesn't infer that the payload.name is of correct type
                    name: action.payload.name
                }
            }
        }
    }

    return state;
}

// This infers the name in the payload we can pass, and we can never pass the wrong value
reducer({CursorTools: {name: 'Cursor'}, DrawingTools: {name: 'Brush'}
}, {type: 'CursorTools', payload: {
    name: 'Cursor'
}})

CodePudding user response:

The following would do the trick:

const reducer = <T extends ToolCategory, S extends ToolState>(
    state: S,
    action: ToolAction<T>
): S => {
    switch(action.type){
        case 'CursorTools': {
            return {
                ...state,
                CursorTools: {
                    name: action.payload.name
                }
            }
        }
    }

    return state;
}
  • Related