Coffee11
Coffee117mo ago

Need guidance on return types of query

Hi, I need help understanding why the return type of the given query contains null in the object return of get despite having a null check?
const userFavoriteCollections = await ctx.db
.query("favorite_listings")
.withIndex("by_user_id")
.filter((q) => q.eq(q.field("userId"), user._id))
.collect();

const userFavorites = await Promise.all(
userFavoriteCollections.map(async (userFavorite) => {
const propertyListing = await ctx.db.get(
userFavorite.propertyListingId
);

if (!propertyListing) {
return null;
}

return { ...propertyListing, favorite: true };
})
);

return userFavorites.filter((userFavorite) => userFavorite !== null);
const userFavoriteCollections = await ctx.db
.query("favorite_listings")
.withIndex("by_user_id")
.filter((q) => q.eq(q.field("userId"), user._id))
.collect();

const userFavorites = await Promise.all(
userFavoriteCollections.map(async (userFavorite) => {
const propertyListing = await ctx.db.get(
userFavorite.propertyListingId
);

if (!propertyListing) {
return null;
}

return { ...propertyListing, favorite: true };
})
);

return userFavorites.filter((userFavorite) => userFavorite !== null);
return type
const getFavoritePropertyListings: RegisteredQuery<"public", EmptyObject, Promise<({
favorite: boolean;
_id: Id<"property_listings">;
_creationTime: number;
pricePerSqm?: number | undefined;
buildingName?: string | undefined;
... 23 more ...;
description: string;
} | null)[]>>
const getFavoritePropertyListings: RegisteredQuery<"public", EmptyObject, Promise<({
favorite: boolean;
_id: Id<"property_listings">;
_creationTime: number;
pricePerSqm?: number | undefined;
buildingName?: string | undefined;
... 23 more ...;
description: string;
} | null)[]>>
but putting "!" solves the issue?
const userFavoriteCollections = await ctx.db
.query("favorite_listings")
.withIndex("by_user_id")
.filter((q) => q.eq(q.field("userId"), user._id))
.collect();

const userFavorites = await Promise.all(
userFavoriteCollections.map(async (userFavorite) => {
const propertyListing = await ctx.db.get(
userFavorite.propertyListingId
);

return { **...propertyListing!**, favorite: true };
})
);

return userFavorites.filter((userFavorite) => userFavorite !== null);
const userFavoriteCollections = await ctx.db
.query("favorite_listings")
.withIndex("by_user_id")
.filter((q) => q.eq(q.field("userId"), user._id))
.collect();

const userFavorites = await Promise.all(
userFavoriteCollections.map(async (userFavorite) => {
const propertyListing = await ctx.db.get(
userFavorite.propertyListingId
);

return { **...propertyListing!**, favorite: true };
})
);

return userFavorites.filter((userFavorite) => userFavorite !== null);
return type
const getFavoritePropertyListings: RegisteredQuery<"public", EmptyObject, Promise<{
favorite: boolean;
_id: Id<"property_listings">;
_creationTime: number;
pricePerSqm?: number | undefined;
buildingName?: string | undefined;
... 23 more ...;
description: string;
}[]>>
const getFavoritePropertyListings: RegisteredQuery<"public", EmptyObject, Promise<{
favorite: boolean;
_id: Id<"property_listings">;
_creationTime: number;
pricePerSqm?: number | undefined;
buildingName?: string | undefined;
... 23 more ...;
description: string;
}[]>>
6 Replies
erquhart
erquhart7mo ago
Array.filter doesn't do type narrowing well. I personally use Array.flatMap instead - just return an empty array in place of false to filter items out. The result will be narrowed the way you're expecting.
Coffee11
Coffee11OP7mo ago
Yep, I'll do this instead. By the way, thank you for the tip!
const userFavoriteCollections = await ctx.db
.query("favorite_listings")
.withIndex("by_user_id")
.filter((q) => q.eq(q.field("userId"), user._id))
.collect();

const userFavorites = await Promise.all(
userFavoriteCollections.map(async (userFavorite) => {
const propertyListing = await ctx.db.get(
userFavorite.propertyListingId
);

if (!propertyListing) {
return null;
}

return { ...propertyListing, favorite: true };
})
);

return userFavorites.filter(
(userFavorite): userFavorite is NonNullable<typeof userFavorite> =>
userFavorite !== null
);
const userFavoriteCollections = await ctx.db
.query("favorite_listings")
.withIndex("by_user_id")
.filter((q) => q.eq(q.field("userId"), user._id))
.collect();

const userFavorites = await Promise.all(
userFavoriteCollections.map(async (userFavorite) => {
const propertyListing = await ctx.db.get(
userFavorite.propertyListingId
);

if (!propertyListing) {
return null;
}

return { ...propertyListing, favorite: true };
})
);

return userFavorites.filter(
(userFavorite): userFavorite is NonNullable<typeof userFavorite> =>
userFavorite !== null
);
ian
ian7mo ago
Wow TIL! I've always been curious about that and ended up writing helper functions to get the types right. thanks!
erquhart
erquhart7mo ago
Yeah type predicate works too, and is more idiomatic than abusing flatmap lol
Coffee11
Coffee11OP7mo ago
flatMap what's magical about this it works as well!
erquhart
erquhart7mo ago
Honestly, far as I understand, they just typed it better than filter, they should both work this well

Did you find this page helpful?