#How to distinguish between types when defining multiple on a function param

49 messages · Page 1 of 1 (latest)

winged grove
#

I have a function that can accept string | Listing | FeedItem. If it is a string I want to fetch it (because it is assumed the ID was passed), otherwise I need to be able to distinguish between FeedItem and Listing in code to determine what to do

#

To add -- The only difference between Listing and FeedItem is that FeedItem extends Listing and adds an owner property

#

I have this:

            async (item:string | Listing | FeedItem) => {
                let listing
                if (typeof item === 'string') {
                    const fetched = await getDoc(doc(listings, item)).then(i => i.data())
                    if (fetched) listing = {...fetched}
                } else {
                    listing = item
                }
                if (!listing) return

                const foo = listing
            }

But for some reason foo is typed as only Listing instead of both FeedItem and Listing

#

The getDoc call returns Listing | undefined hence the check

#

Feel like I am blanking out because this should be simple

#

Switched to a discriminated type:

type UpdateParams = {
    type: 'id';
    item: string;
} | {
    type: 'listing';
    item: Listing;
} | {
    type: 'feedItem';
    item: FeedItem
}

But this just seems... extra? Would be interested in a better way (if there is one)

bitter plume
#

there is nothing specific to TypeScript really
you'd do it the exact same way in JS:
comparing two raw objects
checking if those have some properties set, etc.

#

there is a list with all the possible way you can tell different values appart

#

that concept is called narrowing

#

!hb narrowing

hard scaffoldBOT
bitter plume
#

in your case, you could simply use the JS in operator, to see if a property is present on the object or not

#

if it is, then you (and TS) know it's an object of such type
and if not, then the other type

#

@winged grove

#

but using a discriminator (extra property in commo with unioque value for each type) (what you did) is another possible way of doing things

winged grove
#

When testing using in is it common to have to cast the base type to the value after?

bitter plume
#

wdym exactly? MeowThink

winged grove
#

Because I can check for 'owner' in listing, but it doesn't infer the FeedItem type from that, even though it is the only possible option

bitter plume
#

think it should work

winged grove
#

Oh wait, it is now

#

I must've done something wonky before

hard scaffoldBOT
#
ascor8522#0

Preview:```ts
interface Listing {
id: string

owner: string
foo: string
}
interface FeedItem {
id: string

bar: string
}

declare const element: Listing | FeedItem

if ("owner" in element) {
element.foo // is of type Listing, can access unique property "foo
...```

bitter plume
#

!ts

hard scaffoldBOT
#
interface Listing {
  id: string;

  owner: string;
  foo: string;
}
interface FeedItem {
  id: string;

  bar: string;
}

declare const element: Listing | FeedItem;

if("owner" in element) {
  element.foo; // is of type Listing, can access unique property "foo"
} else {
  element.bar; // is of type FeedItem, can access unique property "bar"
}
winged grove
bitter plume
#

what part exactly?

#

also, what's the error?

#

(maybe you could post the code in the TS playground and share the link)

winged grove
#

Look at the type of foo in the if scope, then the type of bar in the function scrope

bitter plume
#

oh, yeah, it's normal

#

the scope is different

#

you have no guarantee the object is still the same

winged grove
#

lol wut

bitter plume
#

that lambda could be called at any point in time

#

and the check might not be relelvant anymore

bitter plume
winged grove
#

If I assign it to its own variable inside the if check it stays the same in the new scope?

bitter plume
#

TS doesn't know when that function will run
no guarantee the object will still be the same

winged grove
#

Sorry, I know screenshots aren't ideal, but it is the easiest way to share what I'm seeing in my IDE. BUT:

#

That guarantee is there when I assign it to a variable? I just don't understand the difference between that and just using listing outright

bitter plume
#
interface Listing {
  id: string;

  owner: string;
  foo: string;
}
interface FeedItem {
  id: string;

  bar: string;
}


declare const listing: FeedItem | Listing;
const fn = () => {
  listing;
  // ^?
};
#

!ts

hard scaffoldBOT
#
interface Listing {
  id: string;

  owner: string;
  foo: string;
}
interface FeedItem {
  id: string;

  bar: string;
}


declare const listing: FeedItem | Listing;
const fn = () => {
  listing;
//   ^? - const listing: FeedItem | Listing
};
winged grove
#

Right, I understand that much of it

#

Also what is the point of declare there

bitter plume
# winged grove Also what is the point of declare there

tells the compiler a variable exists with such type
but no need to provide actual implementation
(only/mainly used in playground, so you don't need to recreate and initiliaze everything)
(also in typings file where you tell TS those objects exist)

winged grove
#

ahh

bitter plume