5

I'm building a React component that would have a different HTML tag based on a property a user passed. Here's an example:

interface Common {
    common?: number;
}

interface A extends Common {
    first?: string;
    second?: string;
    check: true;
}

interface B extends Common {
    third?: string;
    fourth?: string;
    check?: false;
}

type Test = A | B;

To test this, I'm expecting the following:

// Success
let test: Test = {
    common: 1,
    check: true,
    first: "text"
}
// Success
let test: Test = {
    common: 1,
    check: false,
    third: "text"
}
// Fail
let test: Test = {
    common: 1,
    check: true,
    third: "text"
}
// Fail
let test: Test = {
    common: 1,
    check: false,
    first: "text"
}

All of that is working, the challenge is to get type B without passing a value to check at all, like such:

let test: Test = {
    common: 1,
    first: "text", // should highlight an error because `check` is not passed
    third: "text",
}

Here is what I tried:

// Attempt 1: is to include possible values
interface B extends Common {
    third?: string;
    fourth?: string;
    check?: false | null | undefined | never | unknown | void; // tried them all
}

// Attempt 2: is not to include at all. Still didn't work
interface B extends Common {
    third?: string;
    fourth?: string;
}
4
  • Why would it error? test obj conforms to to B interface. Commented May 14, 2020 at 11:22
  • 2
    Instead of check?: false you can do: check: false, to distinguish between those two interfaces. Commented May 14, 2020 at 11:29
  • @r3dst0rm It will work regardless when you pass true or false to the check. But the problem that I'm facing is that I want to infer that's it is of type B when I don't even pass the value of check. I tried this: check: false | undefined as in the value is not passed so it's undefined. But it didn't work Commented May 14, 2020 at 14:54
  • What if you remove the check from B altogether and the way to distinguish between the two would be to see if the check exists or not? Commented May 15, 2020 at 2:02

3 Answers 3

2

That is because A | B is inclusive OR, but you need exclusive OR. You can use npm package ts-xor.

Sign up to request clarification or add additional context in comments.

Comments

0

It is not possible to achieve this statically. You need to use type guards, so you first have a Test type what is checked statically, but then you need to check it via guard to have the right typing:

function isB(param: A | B): param is B {
  return (param as B).checked === false;
}

1 Comment

This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
-1

Since Check is common between the two interfaces. Why not

interface Common {
    common?: number;
    check?: boolean;
}


interface A extends Common {
    first?: string;
    second?: string;
}

interface B extends Common {
    third?: string;
    fourth?: string;
}

type Test = A | B;

Instead ?

1 Comment

It's required in A while it's not in B. You effectively removed check altogether. All his test cases are not working anymore.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.