Why the provided union of interfaces does not work as expected?

Viewed 31

The last execution of the test function is causing errors:

type ToggleFunc = (a: string) => void;

type X = { toggle: ToggleFunc; handleClose: () => void };

interface ModalWithTriggerProps {
  trigger: ({ toggle }: { toggle: ToggleFunc }) => void;
  children: (options: X) => void;
}

interface ModalWithoutTriggerProps {
  trigger?: undefined;
  children: (options: { toggle?: undefined; handleClose: () => void }) => void;
}

type ModalPropsX = ModalWithTriggerProps | ModalWithoutTriggerProps;

function test (x: ModalPropsX) {
    if(x.trigger) {
        x.children({toggle: (a: string) => console.log(a), handleClose: () => {}});
    } else {
        x.children({handleClose: () => {}});
    }
}

// with the trigger and extra X typing it works
test({
    trigger: ({toggle}) => {console.log(toggle)},
    children: ({handleClose, toggle}: X) => { console.log( handleClose, toggle )},
});

// without trigger and without extra typing it works
test({
    children: ({handleClose, toggle}) => { console.log( handleClose, toggle )},
});


// it causes errors when with trigger and no extra (X) typing provided
test({
    trigger: ({toggle}) => {console.log(toggle)},
    children: ({handleClose, toggle}) => { console.log( handleClose, toggle )},
});

Errors:

Binding element 'handleClose' implicitly has an 'any' type.
Binding element 'toggle' implicitly has an 'any' type.

Is there a way to fix it somehow? Typescript playground link

1 Answers

I think (think) the problem is that you end up with a union of function types for children, which tends to be ugly.

You can solve it by using function overloads:

function test (x: ModalWithTriggerProps): void;
function test (x: ModalWithoutTriggerProps): void;
function test (x: ModalWithTriggerProps | ModalWithoutTriggerProps): void {
    if(x.trigger) {
        x.children({toggle: (a: string) => console.log(a), handleClose: () => {}});
    } else {
        x.children({handleClose: () => {}});
    }
}

Your last call works just fine with that, inferring all the types:

test({
    trigger: ({toggle}) => {console.log(toggle)},
    children: ({handleClose, toggle}) => { console.log( handleClose, toggle )},
});

Playground link

Related