Search code examples
typescriptinterfacefunction-parameter

Typescript Error : Declaring a parameter as a union of interfaces. Instead of picking one of the choices, it picks all of them


This is the code I want to write

interface InputField{
    formHeading:string,
    isText:boolean,
    fields:any[]
}

interface InfoField{
    formHeading:string,
    isText:boolean,
    textData:string[]
}

let testInfoField: InputField|InfoField;

testInfoField = {
    formHeading : "hello",
    isText : true,
    textData : ['hi', 'hello']
};

console.log(testInfoField.textData)
// Works fine here

let testInputField: InputField|InfoField;
testInputField = {
    formHeading : "Hi",
    isText : false,
    fields: [1,2,3,4]
}

console.log(testInputField.fields);
// Works fine here



const func = (Field:InfoField|InputField) =>{
    if(Field.isText){
        console.log("TextField text", Field.textData);
        // Error in using Field.textData now

    }
    else{
        console.log("Input Field", Field.fields);
        // Error in using Field.inputField now
    }
}

So, according to what I know in Typescript using union means you can use any of the interfaces. So, why it's not working in the function func? The error I got was :

Property 'textData' does not exist on type 'InputField | InfoField'. Property 'textData' does not exist on type 'InputField'. Property 'inputField' does not exist on type 'InputField | InfoField'. Property 'inputField' does not exist on type 'InputField'.

Can anyone tell me the reason for this? If I am using textData, then that means I want to use the InfoField interface, not InputField. It shouldn't matter if it doesn't exist on the InputField.

The function parameter value will automatically come as one of the two choices.

Why when used as a parameter, it is demanding a combined object? It's like it creates a big object consisting of all values of InfoField and InputField.


Solution

  • The problem is that both of your types have isText: boolean, so either of them could have true or false for that, so if (Field.isText) doesn't narrow the union type.

    You need to declare the type of isText as a literal true or false in order to create a discriminated union:

    interface InputField{
        formHeading:string,
        isText:false, // ***
        fields:any[]
    }
    
    interface InfoField{
        formHeading:string,
        isText:true,  // ***
        textData:string[]
    }
    

    Playground link


    Side note: The playground link shows that using Field.textData works with the change. Using Field.inputField still doesn't work, because neither of the union types has a property called inputField. You probably meant fields.