TypeScript type error

Hi guys! I have a type error where I'm at a loss on how to proceed. I'm writing a discord bot that fetches messages in a channel, filters them, and then transforms the data for my backend models. Please see an example of the filter function:
/**
* Filters an array of messages
* @param messages An array of `Message`
* @param pivot
* @returns An array of `Message` that are written by a user, contain text, and hav a specific reaction
*/
const getQualifyingMessages = async (
channel: TextChannel,
pivot?: string,
): Promise<Message[]> => {
// there's more but unimportant...

return messages.filter((message) => {
return (
isTextMessage(message) &&
!isBotMessage(message) &&
!!getMessageReaction(message) // If message contains specified reaction
);
});
};
/**
* Filters an array of messages
* @param messages An array of `Message`
* @param pivot
* @returns An array of `Message` that are written by a user, contain text, and hav a specific reaction
*/
const getQualifyingMessages = async (
channel: TextChannel,
pivot?: string,
): Promise<Message[]> => {
// there's more but unimportant...

return messages.filter((message) => {
return (
isTextMessage(message) &&
!isBotMessage(message) &&
!!getMessageReaction(message) // If message contains specified reaction
);
});
};
I'm filtering for 3 things: 1) if the message is a text message, 2) if the message is written by a user, 3) if the message contains a specific reaction Here is the getMessageReaction() method:
/**
* Get specific reaction on a message
* @param message A text message
* @param filter The name of the reaction or emoji to filter for
* @returns A specific reaction or null if one isn't found
*/
const getMessageReaction = (
message: Message,
filter: string = "💹",
): MessageReaction | null => {
const [...all] = message.reactions.cache.values();

const find = all.filter((reaction) => {
const name = reaction.emoji.name ?? "";
return filter.includes(name);
});

return find.pop() ?? null;
};
/**
* Get specific reaction on a message
* @param message A text message
* @param filter The name of the reaction or emoji to filter for
* @returns A specific reaction or null if one isn't found
*/
const getMessageReaction = (
message: Message,
filter: string = "💹",
): MessageReaction | null => {
const [...all] = message.reactions.cache.values();

const find = all.filter((reaction) => {
const name = reaction.emoji.name ?? "";
return filter.includes(name);
});

return find.pop() ?? null;
};
As you can see, I'm checking to see if the message contains a specific reaction via filter and returning null if not. The issue is when I go to mutate my data.
8 Replies
vince
vinceOP3w ago
When I go to mutate my data, I get the error: Argument of type 'MessageReaction | null' is not assignable to parameter of type 'MessageReaction'. Here's the mutate method:
const mutateMessage = (message: Message): IMessage => {
return {
id: message.id,
author: mutateMember(message),
content: message.content,
reaction: mutateReaction(getMessageReaction(message)),
date: message.createdAt,
url: message.url,
};
};
const mutateMessage = (message: Message): IMessage => {
return {
id: message.id,
author: mutateMember(message),
content: message.content,
reaction: mutateReaction(getMessageReaction(message)),
date: message.createdAt,
url: message.url,
};
};
Self explanatory because getMessageReaction() returns MessageReaction | null, but I don't know how to proceed here because my getQualifyingMessages() method should already be checking to see if the message it has the specified reaction. I could just type assert as MessageReaction or ! but I don't want to do that I'm essentially asking for a code review at this point because I definitely could have written this better as to not get this, so I understand I'll be lucky if someone wants to help out. Here's the file for reference: https://github.com/v11ncent/discord-yakuza/blob/feature/database/src/commands/leaderboard/initialize.ts As a workaround, I ended up removing the check for if the message contains the specified reaction in getQualifyingMessages(), and instead added the check in the parent block:
messages.forEach((message) => {
const reaction = getMessageReaction(message);
if (!reaction) return;

const mutatedReaction = mutateReaction(reaction);
const mutatedMessage = mutateMessage(message, mutatedReaction);

leaderboard.push({
member: mutateMember(message),
message: mutatedMessage,
count: mutatedReaction.count,
});
});

//...
messages.forEach((message) => {
const reaction = getMessageReaction(message);
if (!reaction) return;

const mutatedReaction = mutateReaction(reaction);
const mutatedMessage = mutateMessage(message, mutatedReaction);

leaderboard.push({
member: mutateMember(message),
message: mutatedMessage,
count: mutatedReaction.count,
});
});

//...
I also changed the mutateMessage method to take an IReaction argument, which I'm not particularly thrilled about either. I really don't like this solution because I feel like it's semantically better to check for this in the getQualifyingMessages method but I'm not able to get TS to infer it correctly.
glutonium
glutonium3w ago
just use ! there's nothing wrong with it ts can only take u so far there's nothing wrong with using ! in situations where u KNOW it's not null
vince
vinceOP3w ago
That makes sense but I think there's a way I can rewrite this and have TS infer
glutonium
glutonium3w ago
i mean , i would not waste time bringing any extra complexity just so i dont have to use ! where i am SURE there is no issue using it but if there are going to be other benefits to it (by rewriting) then sure
vince
vinceOP3w ago
Difference of philosophy, if this was an actual project for a client I'd be inclined to agree that it's not worth the effort since it's taking me so long but I'm doing this to learn
glutonium
glutonium3w ago
gotcha
dys 🐙
dys 🐙3w ago
I'd agree. The only time I'll use ! is when a check that verifies the presence has been run, but TypeScript isn't smart enough to recognize it has happened. You have values nullishly coalescing to the empty string which you then pass to .includes(). That effectively disables the filter. Is that what you want? You need to handle null values in mutateReaction because it's entirely possible one will be passed in from getMessageReaction. I'm not sure I understand your question.
vince
vinceOP3w ago
Hmm I think my issue comes from wanting to include the check in getQualifyingMessages() where it'll check if the message has a reaction. I'm not actually saving / returning that reaction fromthat function, so when I go to call getMessageReaction() later, TS can't infer that we already checked it. I think I'll just keep the check in the parent block like I did unless you have a better idea

Did you find this page helpful?