2

So I'm trying to write a prop type format wherein if one was chosen the other prop will be discarded.

type ButtonProps = {
    to: string,
} | {
    onClick: (() => void) 
};

export const BackButton = (props: ButtonProps) => {
  if(props.to != null) {
     //props {to} will be used hence no need for onClick
  }
  else {
    // {onClick} will be used over {to} 
  }
}

But it says

Property 'to' does not exist on type 'ButtonProps'. Property 'to' does not exist on type '{ onClick: () => void; }'.ts(2339`

How to format type shape with OR so when either one is chosen the other will be discarded. No optionals, the chosen prop will be required.

swoopy
  • 326
  • 4
  • 18
  • You can make `to` as optional property `type ButtonProps { to?: string, onClick?: () => void }`. Also, i guess interface would suit here better – Rajesh Nov 27 '19 at 11:38

1 Answers1

5

We need to use type guards to properly narrow the type by the condition. In order to do so, we need to split the type little bit, in order to assert in type guard + readability. Below ButtonProps is equal with your implementation, but has elements of the union specified explicitly.

Second thing is a type guard, in the snippet below isButtonWithTo is exactly that. It narrows the type to one of options in the union. Pay attention to is keyword, it is indicating what means that function evaluates to true, in this case I am saying that if isButtonWithTo will return true, then argument has a type ButtonWithTo

type ButtonWithTo = {
    to: string,
}
type ButtonWithClick = {
    onClick: (() => void) 
}
// the same type as orginal but with explicit declarations of union elements
type ButtonProps = ButtonWithTo | ButtonWithClick 


const isButtonWithTo = (b: ButtonProps): b is ButtonWithTo  => 'to' in b // type guard

export const BackButton = (props: ButtonProps) => {
  if(isButtonWithTo(props)) {
     props // props have type ButtonWithTo
  } else {
    props // props have type ButtonWithClick
  }
}
Maciej Sikora
  • 19,374
  • 4
  • 49
  • 50