Home > Back-end >  Typescript with multiple types, how to determine which type has certain props
Typescript with multiple types, how to determine which type has certain props

Time:11-16

Lets say I have 3 types. typeA, typeB, and typeC

typeA has a name property while typeB and typeC does not

when I render this with a code like;

interface propTypes {
 post: typeA | typeB | typeC
}

..... some react code ....
return( 
{post?.name && component})

But it returns an error: "Property 'name' does not exist on type 'typeA| typeB | typeC

I tried post instanceof typeA but it returns an error 'typeA' only refers to a type, but is being used as a value here.

CodePudding user response:

Consider this example:

type typeA = {
  name: string
}

type typeB = {
  surname: string
}

type typeC = {
  age: number
}

type post = typeA | typeB | typeC

interface propTypes {
  post: post
}

const App = ({ post }: propTypes) => {
  post // no autocomplete

  return null
}

There is no autocomplete after post. because you don't have any common props.

See docs

If we have a value that is a union type, we can only access members that are common to all types in the union.

In order to make it work, you should either define a typeguard:

const isTypeA = (props: unknown): props is typeA => Object.prototype.hasOwnProperty.call(props, 'name')

const App = ({ post }: propTypes) => {
  if (isTypeA(post)) {
    post.name // string
  }

  return null
}

OR, you can use in operator, it works very nice with unions:

const App = ({ post }: propTypes) => {
  if ('name' in post) {
    post.name // string
  }

  return null
}

OR you can use StrictUnion type:

type typeA = {
  name: string
}

type typeB = {
  surname: string
}

type typeC = {
  age: number
}

// credits goes to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
  T extends any
  ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;

type StrictUnion<T> = StrictUnionHelper<T, T>

type Post = StrictUnion<typeA | typeB | typeC>


interface propTypes {
  post: Post
}


const App = ({ post }: propTypes) => {
  if (post.name) {
    post.name // string
  }

  return null
}

Playground


the 'in' operator worked for me Please keep in mind that it has own drawbacks:

type typeA = {
  name?: string
}

interface propTypes {
  post: typeA
}


const App = ({ post }: propTypes) => {
  if ('name' in post) {
    post.name.charAt // error
  }

  return null
}

It works perfectly fine with unions, but not with optional properties.

  • Related