Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my TypeScript report an error for a union type?

Tags:

I have:

type: 'WEBSOCKET' | 'HTTP_STREAM';
connection: WebSocketType | HTTPStreamType;

where:

interface Header {
  key: string;
  val: string;
}

export interface WebSocketType {
  websocketUrl: string;
}

export interface HTTPStreamType {
  streamUrl: string;
  method: 'get' | 'put';
  headers: Header[];
}

I'm trying:

        if (newStream.type === 'WEBSOCKET') {
          if (newStream?.connection?.websocketUrl?.length > 0) {
            return setIsNextDisabled(false)
          }
        }

but the error I get is:

Property 'websocketUrl' does not exist on type 'WebSocketType | HTTPStreamType'.
  Property 'websocketUrl' does not exist on type 'HTTPStreamType'.

I thought the guard would work, but it doesn't.

like image 827
Shamoon Avatar asked Jun 12 '20 20:06

Shamoon


2 Answers

I assume you have something like:

interface MyData {
    type: 'WEBSOCKET' | 'HTTP_STREAM';
    connection: WebSocketType | HTTPStreamType;
}

According to this interface, you could have a type of WEBSOCKET and a connection of the HTTPStreamType. Nowhere have you guaranteed any linkage between members of each union. type and connection could both be either member of the union.

That is is why typescript doesn't think your guard has helped.

Instead, you want a different kind of union:

type MyData = {
    type: 'WEBSOCKET';
    connection: WebSocketType;
} | {
    type: 'HTTP_STREAM';
    connection: HTTPStreamType;
}

This type says that MyData could be a type WEBSOCKET with a WebSocketType connection, or it could be a type HTTP_STREAM with a HTTPStreamType connection. But it could never mix one property from the first, and another from the second.

Now typescript can infer that a a check against one of the properties allows the other property type to be known.

Playground with code that passes type checking.


With some refactoring you can avoid repeating common properties like so.

interface MyCommonData {
    foo: string
    bar: number
}

interface WebSocketData extends MyCommonData {
    type: 'WEBSOCKET'
    connection: WebSocketType
}

interface HttpStreamData extends MyCommonData {
    type: 'HTTP_STREAM';
    connection: HTTPStreamType;
}

type MyData = WebSocketData | HttpStreamData
like image 174
Alex Wayne Avatar answered Sep 22 '22 11:09

Alex Wayne


Union types must have some common fields, in your case something like this would work

interface Header {
  key: string;
  val: string;
}

export interface WebSocketType {
  url: string;
}

export interface HTTPStreamType {
  url: string;
  method: 'get' | 'put';
  headers: Header[];
}

export class Stream { 

  constructor(
    public _type: 'WEBSOCKET' | 'HTTP_STREAM',
    public connection: WebSocketType  | HTTPStreamType
  ) { }
}




let newStream = new Stream('WEBSOCKET', { url: 'myurl' })
let newStream2 = new Stream(
  'HTTP_STREAM',
  { url: 'meaw', method: 'get', headers: [
    { key: 'someheader', val: 'somevalue' }
  ]
  })

if (newStream._type === 'WEBSOCKET') {
  if (newStream.connection.url.length > 0) {
    //do stuff
  }
}

if (newStream2._type === 'HTTP_STREAM') {
  if (newStream2.connection.url.length > 0) {
    //do stuff
  }
}
like image 45
andrès coronado Avatar answered Sep 22 '22 11:09

andrès coronado