diff --git a/.changeset/easy-laws-talk.md b/.changeset/easy-laws-talk.md
new file mode 100644
index 0000000000000..51ff0a2702f2b
--- /dev/null
+++ b/.changeset/easy-laws-talk.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes non-deterministic comparator in team's channel desertion table
diff --git a/apps/meteor/app/api/server/v1/invites.ts b/apps/meteor/app/api/server/v1/invites.ts
index 0e0ea055160ee..86a971a1b2d3d 100644
--- a/apps/meteor/app/api/server/v1/invites.ts
+++ b/apps/meteor/app/api/server/v1/invites.ts
@@ -250,6 +250,7 @@ const invites = API.v1
},
async function action() {
const { token } = this.bodyParams;
+ // eslint-disable-next-line react-hooks/rules-of-hooks
return API.v1.success(await useInviteToken(this.userId, token));
},
)
diff --git a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts
index bab1ed08213e1..d3207b9205930 100644
--- a/apps/meteor/app/autotranslate/client/lib/autotranslate.ts
+++ b/apps/meteor/app/autotranslate/client/lib/autotranslate.ts
@@ -69,6 +69,16 @@ export const AutoTranslate = {
}
}
+ if (attachment.description && attachment.translations && attachment.translations[language]) {
+ attachment.translations.original = attachment.description;
+
+ if (autoTranslateShowInverse) {
+ attachment.description = attachment.translations.original;
+ } else {
+ attachment.description = attachment.translations[language];
+ }
+ }
+
if (attachment.attachments && attachment.attachments.length > 0) {
// @ts-expect-error - not sure what to do with this
attachment.attachments = this.translateAttachments(attachment.attachments, language);
diff --git a/apps/meteor/app/autotranslate/server/autotranslate.ts b/apps/meteor/app/autotranslate/server/autotranslate.ts
index 3e04f6d39eb30..2f91e02463d58 100644
--- a/apps/meteor/app/autotranslate/server/autotranslate.ts
+++ b/apps/meteor/app/autotranslate/server/autotranslate.ts
@@ -320,7 +320,7 @@ export abstract class AutoTranslate {
if (message.attachments && message.attachments.length > 0) {
setImmediate(async () => {
for (const [index, attachment] of message.attachments?.entries() ?? []) {
- if (attachment.text) {
+ if (attachment.description || attachment.text) {
// Removes the initial link `[ ](quoterl)` from quote message before translation
const translatedText = attachment?.text?.replace(/\[(.*?)\]\(.*?\)/g, '$1') || attachment?.text;
const attachmentMessage = { ...attachment, text: translatedText };
diff --git a/apps/meteor/app/autotranslate/server/deeplTranslate.ts b/apps/meteor/app/autotranslate/server/deeplTranslate.ts
index 35f73e1755da6..d76a7ea2e4901 100644
--- a/apps/meteor/app/autotranslate/server/deeplTranslate.ts
+++ b/apps/meteor/app/autotranslate/server/deeplTranslate.ts
@@ -196,7 +196,7 @@ class DeeplAutoTranslate extends AutoTranslate {
params: {
auth_key: this.apiKey,
target_lang: language,
- text: attachment.text || '',
+ text: attachment.description || attachment.text || '',
},
});
if (!result.ok) {
diff --git a/apps/meteor/app/autotranslate/server/googleTranslate.ts b/apps/meteor/app/autotranslate/server/googleTranslate.ts
index 53b9bb7c1d5ea..9667ae53c967a 100644
--- a/apps/meteor/app/autotranslate/server/googleTranslate.ts
+++ b/apps/meteor/app/autotranslate/server/googleTranslate.ts
@@ -195,7 +195,7 @@ class GoogleAutoTranslate extends AutoTranslate {
key: this.apiKey,
target: language,
format: 'text',
- q: attachment.text || '',
+ q: attachment.description || attachment.text || '',
},
});
if (!result.ok) {
diff --git a/apps/meteor/app/autotranslate/server/msTranslate.ts b/apps/meteor/app/autotranslate/server/msTranslate.ts
index 6508734a1c0da..ddb345d3c895a 100644
--- a/apps/meteor/app/autotranslate/server/msTranslate.ts
+++ b/apps/meteor/app/autotranslate/server/msTranslate.ts
@@ -192,7 +192,7 @@ class MsAutoTranslate extends AutoTranslate {
return this._translate(
[
{
- Text: attachment.text || '',
+ Text: attachment.description || attachment.text || '',
},
],
targetLanguages,
diff --git a/apps/meteor/app/lib/server/functions/notifications/email.js b/apps/meteor/app/lib/server/functions/notifications/email.js
index 4de543abb7dd6..c41445fcf55b2 100644
--- a/apps/meteor/app/lib/server/functions/notifications/email.js
+++ b/apps/meteor/app/lib/server/functions/notifications/email.js
@@ -77,8 +77,13 @@ export async function getEmailContent({ message, user, room }) {
}
if (hasFiles) {
- const fileParts = files.map((file) => {
- return escapeHTML(file.name);
+ const attachments = message.attachments || [];
+ const fileParts = files.map((file, index) => {
+ let part = escapeHTML(file.name);
+ if (attachments[index]?.description) {
+ part += `
${escapeHTML(attachments[index].description)}`;
+ }
+ return part;
});
contentParts.push(fileParts.join('
'));
}
diff --git a/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts b/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
index 498cef1624624..7a089abba0815 100644
--- a/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
+++ b/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts
@@ -195,6 +195,8 @@ export const sendNotification = async ({
const firstAttachment = message.attachments?.length && message.attachments.shift();
if (firstAttachment) {
+ firstAttachment.description =
+ typeof firstAttachment.description === 'string' ? emojione.shortnameToUnicode(firstAttachment.description) : undefined;
firstAttachment.text = typeof firstAttachment.text === 'string' ? emojione.shortnameToUnicode(firstAttachment.text) : undefined;
}
diff --git a/apps/meteor/app/lib/server/methods/updateMessage.ts b/apps/meteor/app/lib/server/methods/updateMessage.ts
index 833b4403c0eca..45ba42f25f000 100644
--- a/apps/meteor/app/lib/server/methods/updateMessage.ts
+++ b/apps/meteor/app/lib/server/methods/updateMessage.ts
@@ -34,7 +34,7 @@ export async function executeUpdateMessage(
// IF the message has custom fields, always update
// Ideally, we'll compare the custom fields to check for change, but since we don't know the shape of
// custom fields, as it's user defined, we're gonna update
- const msgText = originalMessage.msg;
+ const msgText = originalMessage?.attachments?.[0]?.description ?? originalMessage.msg;
if (msgText === message.msg && !previewUrls && !message.customFields) {
return;
}
@@ -86,6 +86,13 @@ export async function executeUpdateMessage(
}
await canSendMessageAsync(message.rid, { uid: user._id, username: user.username ?? undefined, ...user });
+ // It is possible to have an empty array as the attachments property, so ensure both things exist
+ if (originalMessage.attachments && originalMessage.attachments.length > 0 && originalMessage.attachments[0].description !== undefined) {
+ originalMessage.attachments[0].description = message.msg;
+ message.attachments = originalMessage.attachments;
+ message.msg = originalMessage.msg;
+ }
+
message.u = originalMessage.u;
return updateMessage(message, user, originalMessage, previewUrls);
diff --git a/apps/meteor/app/livechat/server/lib/sendTranscript.ts b/apps/meteor/app/livechat/server/lib/sendTranscript.ts
index f52ac3f516710..199275f6a516b 100644
--- a/apps/meteor/app/livechat/server/lib/sendTranscript.ts
+++ b/apps/meteor/app/livechat/server/lib/sendTranscript.ts
@@ -108,7 +108,7 @@ export async function sendTranscript({
const messageType = MessageTypes.getType(message);
- const messageContent = messageType?.system
+ let messageContent = messageType?.system
? DOMPurify.sanitize(`
${messageType.text(i18n.cloneInstance({ interpolation: { escapeValue: false } }).t, message)}}`)
: escapeHtml(message.msg);
@@ -116,6 +116,9 @@ export async function sendTranscript({
let filesHTML = '';
if (message.attachments && message.attachments?.length > 0) {
+ messageContent = message.attachments[0].description || '';
+ escapeHtml(messageContent);
+
for await (const attachment of message.attachments) {
if (!isFileAttachment(attachment)) {
continue;
diff --git a/apps/meteor/app/slackbridge/server/RocketAdapter.ts b/apps/meteor/app/slackbridge/server/RocketAdapter.ts
index 5c46d75368dda..100e6991c3b08 100644
--- a/apps/meteor/app/slackbridge/server/RocketAdapter.ts
+++ b/apps/meteor/app/slackbridge/server/RocketAdapter.ts
@@ -203,11 +203,14 @@ export default class RocketAdapter {
if (rocketMessage.file.name) {
let fileName = rocketMessage.file.name;
- const text = rocketMessage.msg;
+ let text = rocketMessage.msg;
const attachment = this.getMessageAttachment(rocketMessage);
if (attachment) {
fileName = Meteor.absoluteUrl(attachment.title_link);
+ if (!text) {
+ text = attachment.description;
+ }
}
await slack.postMessage(slack.getSlackChannel(rocketMessage.rid), { ...rocketMessage, msg: `${text} ${fileName}` });
diff --git a/apps/meteor/app/ui/client/lib/ChatMessages.ts b/apps/meteor/app/ui/client/lib/ChatMessages.ts
index 70b64201979ba..a6febf3fdfef9 100644
--- a/apps/meteor/app/ui/client/lib/ChatMessages.ts
+++ b/apps/meteor/app/ui/client/lib/ChatMessages.ts
@@ -120,7 +120,7 @@ export class ChatMessages implements ChatAPI {
},
editMessage: async (message: IMessage, { cursorAtStart = false }: { cursorAtStart?: boolean } = {}) => {
this.composer?.uploads.clear();
- const text = (await this.data.getDraft(message._id)) || message.msg;
+ const text = (await this.data.getDraft(message._id)) || message.attachments?.[0]?.description || message.msg;
await this.currentEditingMessage.stop();
diff --git a/apps/meteor/client/apps/gameCenter/GameCenter.tsx b/apps/meteor/client/apps/gameCenter/GameCenter.tsx
index 0955710c0a936..b424ad7515c6c 100644
--- a/apps/meteor/client/apps/gameCenter/GameCenter.tsx
+++ b/apps/meteor/client/apps/gameCenter/GameCenter.tsx
@@ -1,5 +1,5 @@
import type { IExternalComponent } from '@rocket.chat/apps-engine/definition/externalComponent';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRoomToolbox } from '@rocket.chat/ui-contexts';
import { useState } from 'react';
import type { MouseEvent } from 'react';
@@ -18,9 +18,9 @@ const GameCenter = () => {
const result = useExternalComponentsQuery();
- const handleClose = useEffectEvent(() => closeTab());
+ const handleClose = useStableCallback(() => closeTab());
- const handleBack = useEffectEvent((e: MouseEvent) => {
+ const handleBack = useStableCallback((e: MouseEvent) => {
setOpenedGame(undefined);
preventSyntheticEvent(e);
});
diff --git a/apps/meteor/client/components/ConfirmOwnerChangeModal.tsx b/apps/meteor/client/components/ConfirmOwnerChangeModal.tsx
index 8ff91eb9da725..470d041d16fff 100644
--- a/apps/meteor/client/components/ConfirmOwnerChangeModal.tsx
+++ b/apps/meteor/client/components/ConfirmOwnerChangeModal.tsx
@@ -1,5 +1,5 @@
import { Box } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import type { ComponentPropsWithoutRef } from 'react';
import { Trans } from 'react-i18next';
@@ -18,7 +18,7 @@ const ConfirmOwnerChangeModal = ({
onConfirm,
onCancel,
}: ConfirmOwnerChangeModalProps) => {
- const getChangeOwnerRooms = useEffectEvent(() => {
+ const getChangeOwnerRooms = useStableCallback(() => {
if (shouldChangeOwner.length === 0) {
return '';
}
@@ -50,7 +50,7 @@ const ConfirmOwnerChangeModal = ({
);
});
- const getRemovedRooms = useEffectEvent(() => {
+ const getRemovedRooms = useStableCallback(() => {
if (shouldBeRemoved.length === 0) {
return '';
}
diff --git a/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx b/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx
index 93502e17905a2..5557b01bf94ff 100644
--- a/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx
+++ b/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx
@@ -11,7 +11,7 @@ import {
FieldRow,
FieldError,
} from '@rocket.chat/fuselage-forms';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
@@ -69,7 +69,7 @@ const CreateDiscussion = ({
},
});
- const onParentRoomChange = useEffectEvent((room: IRoom | undefined) => {
+ const onParentRoomChange = useStableCallback((room: IRoom | undefined) => {
if (!room) {
return;
}
diff --git a/apps/meteor/client/components/SidebarToggler/SidebarToggler.tsx b/apps/meteor/client/components/SidebarToggler/SidebarToggler.tsx
index 0f85b447d2fdf..d3c37f9e2cc3a 100644
--- a/apps/meteor/client/components/SidebarToggler/SidebarToggler.tsx
+++ b/apps/meteor/client/components/SidebarToggler/SidebarToggler.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEmbeddedLayout } from '@rocket.chat/ui-client';
import { useLayout, useSession } from '@rocket.chat/ui-contexts';
import { memo } from 'react';
@@ -10,7 +10,7 @@ const SideBarToggler = () => {
const isLayoutEmbedded = useEmbeddedLayout();
const unreadMessagesBadge = useSession('unread') as number | string | undefined;
- const toggleSidebar = useEffectEvent(() => sidebar.toggle());
+ const toggleSidebar = useStableCallback(() => sidebar.toggle());
return (
{
+ const handleChangeAvatar = useStableCallback(async (file: File) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = async (): Promise => {
@@ -38,7 +38,7 @@ const RoomAvatarEditor = ({ disabled = false, room, roomAvatar, onChangeAvatar }
});
const [clickUpload, reset] = useSingleFileInput(handleChangeAvatar);
- const clickReset = useEffectEvent(() => {
+ const clickReset = useStableCallback(() => {
reset();
onChangeAvatar(null);
});
diff --git a/apps/meteor/client/components/message/content/MessageActions.tsx b/apps/meteor/client/components/message/content/MessageActions.tsx
index b67538962e26c..cd037d97cbf75 100644
--- a/apps/meteor/client/components/message/content/MessageActions.tsx
+++ b/apps/meteor/client/components/message/content/MessageActions.tsx
@@ -1,6 +1,6 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { Box, ButtonGroup } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { Keys as IconName } from '@rocket.chat/icons';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
@@ -22,7 +22,7 @@ type MessageActionsProps = {
};
const MessageActions = ({ message, actions }: MessageActionsProps) => {
- const runAction = useEffectEvent((action: string) => () => {
+ const runAction = useStableCallback((action: string) => () => {
actionLinks.run(action, message);
});
diff --git a/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx
index 9fa94126127e8..227874cfb8eaf 100644
--- a/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx
+++ b/apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx
@@ -4,13 +4,17 @@ import { useMediaUrl } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
import { useReloadOnError } from './hooks/useReloadOnError';
+import MarkdownText from '../../../../MarkdownText';
import MessageCollapsible from '../../../MessageCollapsible';
+import MessageContentBody from '../../../MessageContentBody';
const AudioAttachment = ({
title,
audio_url: url,
audio_type: type,
audio_size: size,
+ description,
+ descriptionMd,
title_link: link,
title_link_download: hasDownload,
collapsed,
@@ -21,6 +25,7 @@ const AudioAttachment = ({
return (
<>
+ {descriptionMd ? : }
diff --git a/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx b/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx
index d57325334098e..f46b24d8d5634 100644
--- a/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx
+++ b/apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx
@@ -13,7 +13,9 @@ import { useTranslation } from 'react-i18next';
import { getFileExtension } from '../../../../../../lib/utils/getFileExtension';
import { forAttachmentDownload, registerDownloadForUid } from '../../../../../hooks/useDownloadFromServiceWorker';
+import MarkdownText from '../../../../MarkdownText';
import MessageCollapsible from '../../../MessageCollapsible';
+import MessageContentBody from '../../../MessageContentBody';
import AttachmentSize from '../structure/AttachmentSize';
import { useOpenEncryptedPdf } from './hooks/useOpenEncryptedPdf';
@@ -23,6 +25,8 @@ type GenericFileAttachmentProps = MessageAttachmentBase;
const GenericFileAttachment = ({
title,
+ description,
+ descriptionMd,
title_link: link,
title_link_download: hasDownload,
size,
@@ -81,6 +85,7 @@ const GenericFileAttachment = ({
return (
<>
+ {descriptionMd ? : }
+ {descriptionMd ? : }
+ {descriptionMd ? : }
diff --git a/apps/meteor/client/components/message/content/attachments/file/hooks/useReloadOnError.tsx b/apps/meteor/client/components/message/content/attachments/file/hooks/useReloadOnError.tsx
index a826fb81c3dd9..720580d8aa30e 100644
--- a/apps/meteor/client/components/message/content/attachments/file/hooks/useReloadOnError.tsx
+++ b/apps/meteor/client/components/message/content/attachments/file/hooks/useReloadOnError.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent, useSafeRefCallback } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useSafeRefCallback } from '@rocket.chat/fuselage-hooks';
import { useCallback, useRef, useState } from 'react';
const events = ['error', 'stalled', 'play'];
@@ -52,7 +52,7 @@ export const useReloadOnError = (url: string, type: 'video' | 'audio') => {
const isRecovering = useRef(false);
const firstRecoveryAttempted = useRef(false);
- const handleMediaURLRecovery = useEffectEvent(async (event: Event) => {
+ const handleMediaURLRecovery = useStableCallback(async (event: Event) => {
if (isRecovering.current) {
console.debug(`Media URL recovery already in progress, skipping ${event.type} event`);
return;
diff --git a/apps/meteor/client/components/message/toolbar/useCopyAction.ts b/apps/meteor/client/components/message/toolbar/useCopyAction.ts
index b8275144abca1..1a03dac99936d 100644
--- a/apps/meteor/client/components/message/toolbar/useCopyAction.ts
+++ b/apps/meteor/client/components/message/toolbar/useCopyAction.ts
@@ -6,8 +6,8 @@ import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/Me
const getMainMessageText = (message: IMessage): IMessage => {
const newMessage = { ...message };
- newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.title || '';
- newMessage.md = newMessage.md || undefined;
+ newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.description || newMessage.attachments?.[0]?.title || '';
+ newMessage.md = newMessage.md || newMessage.attachments?.[0]?.descriptionMd || undefined;
return { ...newMessage };
};
diff --git a/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx
index dbbb962032907..ba281c5a1f5a2 100644
--- a/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx
+++ b/apps/meteor/client/components/message/toolbar/useReportMessageAction.tsx
@@ -7,8 +7,8 @@ import ReportMessageModal from '../../../views/room/modals/ReportMessageModal';
const getMainMessageText = (message: IMessage): IMessage => {
const newMessage = { ...message };
- newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.title || '';
- newMessage.md = newMessage.md || undefined;
+ newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.description || newMessage.attachments?.[0]?.title || '';
+ newMessage.md = newMessage.md || newMessage.attachments?.[0]?.descriptionMd || undefined;
return { ...newMessage };
};
diff --git a/apps/meteor/client/components/message/variants/RoomMessage.tsx b/apps/meteor/client/components/message/variants/RoomMessage.tsx
index 157856ce3b087..114b8405a9e57 100644
--- a/apps/meteor/client/components/message/variants/RoomMessage.tsx
+++ b/apps/meteor/client/components/message/variants/RoomMessage.tsx
@@ -3,7 +3,7 @@ import { Message, MessageLeftContainer, MessageContainer, CheckBox } from '@rock
import { useToggle } from '@rocket.chat/fuselage-hooks';
import { MessageAvatar } from '@rocket.chat/ui-avatar';
import { useUserId, useUserCard } from '@rocket.chat/ui-contexts';
-import type { ComponentProps } from 'react';
+import type { ComponentProps, KeyboardEvent } from 'react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -88,7 +88,7 @@ const RoomMessage = ({
useCountSelected();
- const handleKeyDown = (e: React.KeyboardEvent) => {
+ const handleKeyDown = (e: KeyboardEvent) => {
if (!selecting) return;
if (!(e.code === 'Space' || e.code === 'Enter')) return;
diff --git a/apps/meteor/client/components/message/variants/SystemMessage.tsx b/apps/meteor/client/components/message/variants/SystemMessage.tsx
index 1ca0d4f8b2925..e78117e1552b4 100644
--- a/apps/meteor/client/components/message/variants/SystemMessage.tsx
+++ b/apps/meteor/client/components/message/variants/SystemMessage.tsx
@@ -17,7 +17,7 @@ import { UserAvatar } from '@rocket.chat/ui-avatar';
import { useUserDisplayName } from '@rocket.chat/ui-client';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useUserPresence, useUserCard } from '@rocket.chat/ui-contexts';
-import type { ComponentProps } from 'react';
+import type { ComponentProps, KeyboardEvent } from 'react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -64,7 +64,7 @@ const SystemMessage = ({ message, showUserAvatar, ...props }: SystemMessageProps
useCountSelected();
const buttonProps = useButtonPattern((e) => openUserCard(e, user.username));
- const handleKeyDown = (e: React.KeyboardEvent) => {
+ const handleKeyDown = (e: KeyboardEvent) => {
if (!isSelecting) return;
if (!(e.code === 'Space' || e.code === 'Enter')) return;
diff --git a/apps/meteor/client/hooks/menuActions/useLeaveRoom.tsx b/apps/meteor/client/hooks/menuActions/useLeaveRoom.tsx
index 2700bb676a1a4..021ee36b49b53 100644
--- a/apps/meteor/client/hooks/menuActions/useLeaveRoom.tsx
+++ b/apps/meteor/client/hooks/menuActions/useLeaveRoom.tsx
@@ -1,5 +1,5 @@
import type { RoomType } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useEndpoint, useRouter, useSetModal, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -33,7 +33,7 @@ export const useLeaveRoomAction = ({ rid, type, name, roomOpen }: LeaveRoomProps
const leaveRoom = useEndpoint('POST', leaveEndpoints[type]);
- const handleLeave = useEffectEvent(() => {
+ const handleLeave = useStableCallback(() => {
const leave = async (): Promise => {
try {
await leaveRoom({ roomId: rid });
diff --git a/apps/meteor/client/hooks/menuActions/useToggleFavoriteAction.ts b/apps/meteor/client/hooks/menuActions/useToggleFavoriteAction.ts
index 70284057aee78..ece0eef82a08f 100644
--- a/apps/meteor/client/hooks/menuActions/useToggleFavoriteAction.ts
+++ b/apps/meteor/client/hooks/menuActions/useToggleFavoriteAction.ts
@@ -1,12 +1,12 @@
import type { IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
export const useToggleFavoriteAction = ({ rid, isFavorite }: { rid: IRoom['_id']; isFavorite: boolean }) => {
const toggleFavorite = useEndpoint('POST', '/v1/rooms.favorite');
const dispatchToastMessage = useToastMessageDispatch();
- const handleToggleFavorite = useEffectEvent(async () => {
+ const handleToggleFavorite = useStableCallback(async () => {
try {
await toggleFavorite({ roomId: rid, favorite: !isFavorite });
} catch (error) {
diff --git a/apps/meteor/client/hooks/menuActions/useToggleNotificationsAction.ts b/apps/meteor/client/hooks/menuActions/useToggleNotificationsAction.ts
index c99e6720fa2fe..9e51b765d20b2 100644
--- a/apps/meteor/client/hooks/menuActions/useToggleNotificationsAction.ts
+++ b/apps/meteor/client/hooks/menuActions/useToggleNotificationsAction.ts
@@ -1,5 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -14,7 +14,7 @@ export const useToggleNotificationAction = ({ rid, isNotificationEnabled, roomNa
const dispatchToastMessage = useToastMessageDispatch();
const { t } = useTranslation();
- const handleToggleNotification = useEffectEvent(async () => {
+ const handleToggleNotification = useStableCallback(async () => {
try {
await toggleNotification({ roomId: rid, notifications: { disableNotifications: isNotificationEnabled ? '1' : '0' } });
dispatchToastMessage({
diff --git a/apps/meteor/client/hooks/menuActions/useToggleReadAction.ts b/apps/meteor/client/hooks/menuActions/useToggleReadAction.ts
index d2bdfec8fe305..cfea5815112da 100644
--- a/apps/meteor/client/hooks/menuActions/useToggleReadAction.ts
+++ b/apps/meteor/client/hooks/menuActions/useToggleReadAction.ts
@@ -1,5 +1,5 @@
import type { ISubscription } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useRouter, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -21,7 +21,7 @@ export const useToggleReadAction = ({ rid, isUnread, subscription }: ToggleReadA
const unreadMessages = useMarkAsUnreadMutation();
- const handleToggleRead = useEffectEvent(async () => {
+ const handleToggleRead = useStableCallback(async () => {
try {
queryClient.invalidateQueries({
queryKey: ['sidebar/search/spotlight'],
diff --git a/apps/meteor/client/hooks/notification/useDesktopNotification.ts b/apps/meteor/client/hooks/notification/useDesktopNotification.ts
index 45b30baaeb791..b03dbf5b3e7f7 100644
--- a/apps/meteor/client/hooks/notification/useDesktopNotification.ts
+++ b/apps/meteor/client/hooks/notification/useDesktopNotification.ts
@@ -1,5 +1,5 @@
import type { INotificationDesktop } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useUser } from '@rocket.chat/ui-contexts';
import { useNotification } from './useNotification';
@@ -11,7 +11,7 @@ export const useDesktopNotification = () => {
const user = useUser();
const notify = useNotification();
- const notifyDesktop = useEffectEvent(async (notification: INotificationDesktop) => {
+ const notifyDesktop = useStableCallback(async (notification: INotificationDesktop) => {
if (
notification.payload.rid === RoomManager.opened &&
(typeof window.document.hasFocus === 'function' ? window.document.hasFocus() : undefined)
diff --git a/apps/meteor/client/hooks/notification/useNewMessageNotification.ts b/apps/meteor/client/hooks/notification/useNewMessageNotification.ts
index 96d0045afa94e..9b3eb7ab190de 100644
--- a/apps/meteor/client/hooks/notification/useNewMessageNotification.ts
+++ b/apps/meteor/client/hooks/notification/useNewMessageNotification.ts
@@ -1,11 +1,11 @@
import type { AtLeast, ISubscription } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useCustomSound } from '@rocket.chat/ui-contexts';
export const useNewMessageNotification = () => {
const { notificationSounds } = useCustomSound();
- const notifyNewMessage = useEffectEvent((sub: AtLeast) => {
+ const notifyNewMessage = useStableCallback((sub: AtLeast) => {
if (!sub || sub.audioNotificationValue === 'none') {
return;
}
diff --git a/apps/meteor/client/hooks/notification/useNotification.ts b/apps/meteor/client/hooks/notification/useNotification.ts
index 5f2f9ea6891ab..0ef377703fcd6 100644
--- a/apps/meteor/client/hooks/notification/useNotification.ts
+++ b/apps/meteor/client/hooks/notification/useNotification.ts
@@ -1,5 +1,5 @@
import type { INotificationDesktop } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { Random } from '@rocket.chat/random';
import { useRouter, useUserPreference } from '@rocket.chat/ui-contexts';
@@ -14,7 +14,7 @@ export const useNotification = () => {
const router = useRouter();
const notificationAllowed = useNotificationAllowed();
- const notify = useEffectEvent(async (notification: INotificationDesktop) => {
+ const notify = useStableCallback(async (notification: INotificationDesktop) => {
if (!notificationAllowed) {
return;
}
diff --git a/apps/meteor/client/hooks/roomActions/useE2EERoomAction.ts b/apps/meteor/client/hooks/roomActions/useE2EERoomAction.ts
index 6bce502e6d384..603ac9c2a02d0 100644
--- a/apps/meteor/client/hooks/roomActions/useE2EERoomAction.ts
+++ b/apps/meteor/client/hooks/roomActions/useE2EERoomAction.ts
@@ -1,5 +1,5 @@
import { isRoomFederated } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { imperativeModal } from '@rocket.chat/ui-client';
import { useSetting, usePermission, useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type { RoomToolboxActionConfig } from '@rocket.chat/ui-contexts';
@@ -46,7 +46,7 @@ export const useE2EERoomAction = () => {
const canResetRoomKey = enabled && isE2EEReady && (room.t === 'd' || permittedToToggleEncryption) && isE2EERoomNotReady();
- const action = useEffectEvent(async () => {
+ const action = useStableCallback(async () => {
if (enabledOnRoom) {
imperativeModal.open({
component: BaseDisableE2EEModal,
diff --git a/apps/meteor/client/hooks/roomActions/useVideoCallRoomAction.tsx b/apps/meteor/client/hooks/roomActions/useVideoCallRoomAction.tsx
index 7a0f456d68a73..c4e4fdb0d3589 100644
--- a/apps/meteor/client/hooks/roomActions/useVideoCallRoomAction.tsx
+++ b/apps/meteor/client/hooks/roomActions/useVideoCallRoomAction.tsx
@@ -1,5 +1,5 @@
import { isRoomFederated } from '@rocket.chat/core-typings';
-import { useEffectEvent, useStableArray } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useStableArray } from '@rocket.chat/fuselage-hooks';
import { usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts';
import type { RoomToolboxActionConfig } from '@rocket.chat/ui-contexts';
import {
@@ -53,7 +53,7 @@ export const useVideoCallRoomAction = () => {
const disabled = federated || (!!room.ro && !permittedToPostReadonly) || room.archived;
const tooltip = disabled ? t('core.Video_Call_unavailable_for_this_type_of_room') : undefined;
- const handleOpenVideoConf = useEffectEvent(async () => {
+ const handleOpenVideoConf = useStableCallback(async () => {
if (isCalling || isRinging) {
return;
}
diff --git a/apps/meteor/client/hooks/useAppUiKitInteraction.ts b/apps/meteor/client/hooks/useAppUiKitInteraction.ts
index 56d984c709e69..aac3a737ddcad 100644
--- a/apps/meteor/client/hooks/useAppUiKitInteraction.ts
+++ b/apps/meteor/client/hooks/useAppUiKitInteraction.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useStream, useUserId } from '@rocket.chat/ui-contexts';
import type * as UiKit from '@rocket.chat/ui-kit';
import { useEffect } from 'react';
@@ -7,7 +7,7 @@ export const useAppUiKitInteraction = (handleServerInteraction: (interaction: Ui
const notifyUser = useStream('notify-user');
const uid = useUserId();
- const handle = useEffectEvent(handleServerInteraction);
+ const handle = useStableCallback(handleServerInteraction);
useEffect(() => {
if (!uid) {
return;
diff --git a/apps/meteor/client/hooks/useClipboardWithToast.ts b/apps/meteor/client/hooks/useClipboardWithToast.ts
index 67e33d4d4b3cf..0a53767dc701a 100644
--- a/apps/meteor/client/hooks/useClipboardWithToast.ts
+++ b/apps/meteor/client/hooks/useClipboardWithToast.ts
@@ -1,5 +1,5 @@
import type { UseClipboardReturn } from '@rocket.chat/fuselage-hooks';
-import { useClipboard, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useClipboard, useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -8,7 +8,7 @@ export default function useClipboardWithToast(text: string): UseClipboardReturn
const dispatchToastMessage = useToastMessageDispatch();
return useClipboard(text, {
- onCopySuccess: useEffectEvent(() => dispatchToastMessage({ type: 'success', message: t('Copied') })),
- onCopyError: useEffectEvent((e?: Error) => dispatchToastMessage({ type: 'error', message: e })),
+ onCopySuccess: useStableCallback(() => dispatchToastMessage({ type: 'success', message: t('Copied') })),
+ onCopyError: useStableCallback((e?: Error) => dispatchToastMessage({ type: 'error', message: e })),
});
}
diff --git a/apps/meteor/client/hooks/useDecryptedMessage.spec.ts b/apps/meteor/client/hooks/useDecryptedMessage.spec.ts
index 3103e708910f6..5b35e8d6e3352 100644
--- a/apps/meteor/client/hooks/useDecryptedMessage.spec.ts
+++ b/apps/meteor/client/hooks/useDecryptedMessage.spec.ts
@@ -53,7 +53,7 @@ describe('useDecryptedMessage', () => {
it('should handle E2EE messages with attachments', async () => {
(isE2EEMessage as jest.MockedFunction).mockReturnValue(true);
(e2e.decryptMessage as jest.Mock).mockResolvedValue({
- attachments: [{ title: 'Attachment title' }],
+ attachments: [{ description: 'Attachment description' }],
});
const message = { msg: 'Encrypted message with attachment' };
@@ -63,6 +63,7 @@ describe('useDecryptedMessage', () => {
expect(result.current).toBe('E2E_message_encrypted_placeholder');
});
+ expect(result.current).toBe('Attachment description');
expect(e2e.decryptMessage).toHaveBeenCalledWith(message);
});
diff --git a/apps/meteor/client/hooks/useDecryptedMessage.ts b/apps/meteor/client/hooks/useDecryptedMessage.ts
index 771665dc0b631..e560aacc5b111 100644
--- a/apps/meteor/client/hooks/useDecryptedMessage.ts
+++ b/apps/meteor/client/hooks/useDecryptedMessage.ts
@@ -18,11 +18,14 @@ export const useDecryptedMessage = (message: IMessage): string => {
e2e.decryptMessage(message).then((decryptedMsg) => {
if (decryptedMsg.msg) {
setDecryptedMessage(decryptedMsg.msg);
- return;
}
- if (decryptedMsg.attachments && decryptedMsg.attachments.length > 0) {
- setDecryptedMessage(t('Message_with_attachment'));
+ if (decryptedMsg.attachments && decryptedMsg.attachments?.length > 0) {
+ if (decryptedMsg.attachments[0].description) {
+ setDecryptedMessage(decryptedMsg.attachments[0].description);
+ } else {
+ setDecryptedMessage(t('Message_with_attachment'));
+ }
}
});
}, [message, t, setDecryptedMessage]);
diff --git a/apps/meteor/client/hooks/useHideRoomAction.tsx b/apps/meteor/client/hooks/useHideRoomAction.tsx
index 7c9fbb3bea6b8..d68fac71ee34b 100644
--- a/apps/meteor/client/hooks/useHideRoomAction.tsx
+++ b/apps/meteor/client/hooks/useHideRoomAction.tsx
@@ -1,5 +1,5 @@
import type { RoomType } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModalDoNotAskAgain, useDontAskAgain } from '@rocket.chat/ui-client';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useEndpoint, useSetModal, useToastMessageDispatch, useRouter, useUserId } from '@rocket.chat/ui-contexts';
@@ -30,7 +30,7 @@ const CLOSE_ENDPOINTS_BY_ROOM_TYPE = {
export const useHideRoomAction = ({ rid: roomId, type, name }: HideRoomProps, { redirect = true }: HideRoomOptions = {}) => {
const { t } = useTranslation();
const setModal = useSetModal();
- const closeModal = useEffectEvent(() => setModal());
+ const closeModal = useStableCallback(() => setModal());
const dispatchToastMessage = useToastMessageDispatch();
const dontAskHideRoom = useDontAskAgain('hideRoom');
const router = useRouter();
@@ -62,7 +62,7 @@ export const useHideRoomAction = ({ rid: roomId, type, name }: HideRoomProps, {
},
});
- const handleHide = useEffectEvent(async () => {
+ const handleHide = useStableCallback(async () => {
const warnText = roomCoordinator.getRoomDirectives(type).getUiText(UiTextContext.HIDE_WARNING);
if (dontAskHideRoom) {
diff --git a/apps/meteor/client/hooks/useIdleActiveEvents.ts b/apps/meteor/client/hooks/useIdleActiveEvents.ts
index 441c74d3308c5..6d64ba45015a5 100644
--- a/apps/meteor/client/hooks/useIdleActiveEvents.ts
+++ b/apps/meteor/client/hooks/useIdleActiveEvents.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEffect } from 'react';
import { useIdleDetection } from './useIdleDetection';
@@ -29,8 +29,8 @@ export const useIdleActiveEvents = (
onIdleCallback: () => void,
onActiveCallback?: () => void,
) => {
- const stableIdleCallback = useEffectEvent(onIdleCallback);
- const stableActiveCallback = useEffectEvent(onActiveCallback || (() => undefined));
+ const stableIdleCallback = useStableCallback(onIdleCallback);
+ const stableActiveCallback = useStableCallback(onActiveCallback || (() => undefined));
useEffect(() => {
document.addEventListener(`${id}_idle`, stableIdleCallback);
diff --git a/apps/meteor/client/hooks/useIdleConnection.ts b/apps/meteor/client/hooks/useIdleConnection.ts
index 4ae667237a55b..6e89b80e4baca 100644
--- a/apps/meteor/client/hooks/useIdleConnection.ts
+++ b/apps/meteor/client/hooks/useIdleConnection.ts
@@ -1,5 +1,5 @@
import type { IUser } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useConnectionStatus, useSetting } from '@rocket.chat/ui-contexts';
import { useIdleActiveEvents } from './useIdleActiveEvents';
@@ -9,7 +9,7 @@ export const useIdleConnection = (uid: IUser['_id'] | undefined) => {
const allowAnonymousRead = useSetting('Accounts_AllowAnonymousRead');
const { disconnect: disconnectServer, reconnect: reconnectServer } = useConnectionStatus();
- const disconnect = useEffectEvent(() => {
+ const disconnect = useStableCallback(() => {
if (status !== 'offline') {
if (!uid && allowAnonymousRead !== true) {
disconnectServer();
@@ -17,7 +17,7 @@ export const useIdleConnection = (uid: IUser['_id'] | undefined) => {
}
});
- const reconnect = useEffectEvent(() => {
+ const reconnect = useStableCallback(() => {
if (status === 'offline') {
reconnectServer();
}
diff --git a/apps/meteor/client/hooks/useIdleDetection.ts b/apps/meteor/client/hooks/useIdleDetection.ts
index af7eb75f210fb..31242a4518ab8 100644
--- a/apps/meteor/client/hooks/useIdleDetection.ts
+++ b/apps/meteor/client/hooks/useIdleDetection.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEffect, useRef } from 'react';
const events = ['mousemove', 'mousedown', 'touchend', 'touchstart', 'keypress'];
@@ -34,7 +34,7 @@ export const useIdleDetection = ({
}: UseIdleDetectionOptions = {}) => {
const idleRef = useRef(false);
- const dispatchIdle = useEffectEvent(() => {
+ const dispatchIdle = useStableCallback(() => {
if (idleRef.current) return;
document.dispatchEvent(new Event(`${id}_idle`));
@@ -46,7 +46,7 @@ export const useIdleDetection = ({
idleRef.current = true;
});
- const dispatchActive = useEffectEvent(() => {
+ const dispatchActive = useStableCallback(() => {
if (!idleRef.current) return;
document.dispatchEvent(new Event(`${id}_active`));
diff --git a/apps/meteor/client/hooks/useInfiniteMessageQueryUpdates.ts b/apps/meteor/client/hooks/useInfiniteMessageQueryUpdates.ts
index 3e778a7334915..5daf7990d15e9 100644
--- a/apps/meteor/client/hooks/useInfiniteMessageQueryUpdates.ts
+++ b/apps/meteor/client/hooks/useInfiniteMessageQueryUpdates.ts
@@ -1,5 +1,5 @@
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useStream, useUserId } from '@rocket.chat/ui-contexts';
import type { InfiniteData } from '@tanstack/react-query';
import { useQueryClient } from '@tanstack/react-query';
@@ -21,11 +21,11 @@ export const useInfiniteMessageQueryUpdates = queryKey);
- const doFilter = useEffectEvent(filter);
- const doCompare = useEffectEvent(compare);
+ const getQueryKey = useStableCallback(() => queryKey);
+ const doFilter = useStableCallback(filter);
+ const doCompare = useStableCallback(compare);
- const mutateQueryData = useEffectEvent((mutation: (items: T[]) => void) => {
+ const mutateQueryData = useStableCallback((mutation: (items: T[]) => void) => {
const queryData = queryClient.getQueryData<
InfiniteData<
{
diff --git a/apps/meteor/client/hooks/usePreventPropagation.ts b/apps/meteor/client/hooks/usePreventPropagation.ts
index 19382bec8d100..6d3662681ad3d 100644
--- a/apps/meteor/client/hooks/usePreventPropagation.ts
+++ b/apps/meteor/client/hooks/usePreventPropagation.ts
@@ -1,8 +1,8 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { UIEvent } from 'react';
export const usePreventPropagation = (fn?: (e: UIEvent) => void): ((e: UIEvent) => void) => {
- const preventClickPropagation = useEffectEvent((e: UIEvent): void => {
+ const preventClickPropagation = useStableCallback((e: UIEvent): void => {
e.stopPropagation();
fn?.(e);
});
diff --git a/apps/meteor/client/hooks/useSingleFileInput.ts b/apps/meteor/client/hooks/useSingleFileInput.ts
index 75fef0e0f3df9..c6bc957983088 100644
--- a/apps/meteor/client/hooks/useSingleFileInput.ts
+++ b/apps/meteor/client/hooks/useSingleFileInput.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRef, useEffect } from 'react';
export const useSingleFileInput = (
@@ -63,8 +63,8 @@ export const useSingleFileInput = (
};
}, [fileField, fileType, onSetFile, maxSize, onError]);
- const onClick = useEffectEvent(() => ref?.current?.click());
- const reset = useEffectEvent(() => {
+ const onClick = useStableCallback(() => ref?.current?.click());
+ const reset = useStableCallback(() => {
if (ref.current) {
ref.current.value = '';
}
diff --git a/apps/meteor/client/lib/normalizeThreadMessage.tsx b/apps/meteor/client/lib/normalizeThreadMessage.tsx
index 067a23f6aedfe..f2ee47688a59e 100644
--- a/apps/meteor/client/lib/normalizeThreadMessage.tsx
+++ b/apps/meteor/client/lib/normalizeThreadMessage.tsx
@@ -24,7 +24,11 @@ export function normalizeThreadMessage({ ...message }: Readonly attachment.title);
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
+
+ if (attachment?.description) {
+ return <>{attachment.description}>;
+ }
if (attachment?.title) {
return <>{attachment.title}>;
diff --git a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.spec.ts b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.spec.ts
index 0105608949b7f..e48eb15f885cf 100644
--- a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.spec.ts
+++ b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.spec.ts
@@ -178,6 +178,47 @@ describe('parseMessageTextToAstMarkdown', () => {
});
it('should return correct attachment translated parsed md when translate is active', () => {
+ const attachmentTranslatedMessage = {
+ ...translatedMessage,
+ attachments: [
+ {
+ description: 'description',
+ translations: {
+ en: 'description translated',
+ },
+ },
+ ],
+ };
+ const attachmentTranslatedMessageParsed = {
+ ...translatedMessage,
+ md: translatedMessageParsed,
+ attachments: [
+ {
+ description: 'description',
+ translations: {
+ en: 'description translated',
+ },
+ md: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'description translated',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+
+ expect(parseMessageTextToAstMarkdown(attachmentTranslatedMessage, parseOptions, enabledAutoTranslatedOptions)).toStrictEqual(
+ attachmentTranslatedMessageParsed,
+ );
+ });
+
+ it('should return correct attachment quote translated parsed md when translate is active', () => {
const attachmentTranslatedMessage = {
...translatedMessage,
attachments: [
@@ -337,7 +378,7 @@ describe('parseMessageAttachments', () => {
const attachmentMessage = [
{
- text: 'message **bold** _italic_ and ~strike~',
+ description: 'message **bold** _italic_ and ~strike~',
md: messageParserTokenMessage,
},
];
@@ -359,18 +400,46 @@ describe('parseMessageAttachments', () => {
autoTranslateLanguage: 'en',
};
- it('should return correct attachment text parsed md when translate is active and auto translate language is undefined', () => {
- const textAttachment = [
+ it('should return correct attachment description translated parsed md when translate is active', () => {
+ const descriptionAttachment = [
{
...attachmentMessage[0],
- text: 'attachment not translated',
+ description: 'attachment not translated',
translationProvider: 'provider',
translations: {
en: 'attachment translated',
},
},
];
- const textAttachmentParsed: Root = [
+ const descriptionAttachmentParsed: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'attachment translated',
+ },
+ ],
+ },
+ ];
+
+ expect(parseMessageAttachments(descriptionAttachment, parseOptions, enabledAutoTranslatedOptions)[0].md).toStrictEqual(
+ descriptionAttachmentParsed,
+ );
+ });
+
+ it('should return correct attachment description parsed md when translate is active and auto translate language is undefined', () => {
+ const descriptionAttachment = [
+ {
+ ...attachmentMessage[0],
+ description: 'attachment not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'attachment translated',
+ },
+ },
+ ];
+ const descriptionAttachmentParsed: Root = [
{
type: 'PARAGRAPH',
value: [
@@ -383,11 +452,39 @@ describe('parseMessageAttachments', () => {
];
expect(
- parseMessageAttachments(textAttachment, parseOptions, {
+ parseMessageAttachments(descriptionAttachment, parseOptions, {
...enabledAutoTranslatedOptions,
autoTranslateLanguage: undefined,
})[0].md,
- ).toStrictEqual(textAttachmentParsed);
+ ).toStrictEqual(descriptionAttachmentParsed);
+ });
+
+ it('should return correct attachment text translated parsed md when translate is active', () => {
+ const textAttachment = [
+ {
+ ...attachmentMessage[0],
+ text: 'attachment not translated',
+ translationProvider: 'provider',
+ translations: {
+ en: 'attachment translated',
+ },
+ },
+ ];
+ const textAttachmentParsed: Root = [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'attachment translated',
+ },
+ ],
+ },
+ ];
+
+ expect(parseMessageAttachments(textAttachment, parseOptions, enabledAutoTranslatedOptions)[0].md).toStrictEqual(
+ textAttachmentParsed,
+ );
});
it('should return correct attachment text translated parsed md when translate is active and has multiple texts', () => {
diff --git a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts
index 55d393cee38ed..df84785e26351 100644
--- a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts
+++ b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts
@@ -1,5 +1,12 @@
import type { IMessage, ITranslatedMessage, MessageAttachment } from '@rocket.chat/core-typings';
-import { isE2EEMessage, isQuoteAttachment, isTranslatedAttachment, isTranslatedMessage } from '@rocket.chat/core-typings';
+import {
+ isFileAttachment,
+ isE2EEMessage,
+ isQuoteAttachment,
+ isTranslatedAttachment,
+ isTranslatedMessage,
+ isEncryptedMessageAttachment,
+} from '@rocket.chat/core-typings';
import type { Options, Root } from '@rocket.chat/message-parser';
import { parse } from '@rocket.chat/message-parser';
@@ -51,7 +58,7 @@ export const parseMessageAttachment = (
autoTranslateOptions: { autoTranslateLanguage?: string; translated: boolean },
): T => {
const { translated, autoTranslateLanguage } = autoTranslateOptions;
- if (!attachment.text) {
+ if (!attachment.text && !attachment.description) {
return attachment;
}
@@ -62,8 +69,16 @@ export const parseMessageAttachment = (
const text =
(isTranslatedAttachment(attachment) && autoTranslateLanguage && attachment?.translations?.[autoTranslateLanguage]) ||
attachment.text ||
+ attachment.description ||
'';
+ if (isFileAttachment(attachment) && attachment.description) {
+ attachment.descriptionMd =
+ translated || isEncryptedMessageAttachment(attachment)
+ ? textToMessageToken(text, parseOptions)
+ : (attachment.descriptionMd ?? textToMessageToken(text, parseOptions));
+ }
+
return {
...attachment,
md: translated ? textToMessageToken(text, parseOptions) : (attachment.md ?? textToMessageToken(text, parseOptions)),
diff --git a/apps/meteor/client/lib/userPresence.ts b/apps/meteor/client/lib/userPresence.ts
index 835c3230781e7..af989f09ac8e3 100644
--- a/apps/meteor/client/lib/userPresence.ts
+++ b/apps/meteor/client/lib/userPresence.ts
@@ -1,3 +1,4 @@
+/* eslint-disable react-hooks/rules-of-hooks */
import type { IUser } from '@rocket.chat/core-typings';
import { UserStatus } from '@rocket.chat/core-typings';
import { useConnectionStatus, useIsLoggingIn, useMethod, useUser, useUserPreference } from '@rocket.chat/ui-contexts';
diff --git a/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.spec.ts b/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.spec.ts
index 9584c13531439..184145a6c4506 100644
--- a/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.spec.ts
+++ b/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.spec.ts
@@ -48,7 +48,7 @@ describe('normalizeMessagePreview', () => {
});
describe('when message has attachments', () => {
- it('should return attachment title when description is available', () => {
+ it('should return attachment description when available', () => {
const message = createFakeMessageWithAttachment({
msg: '',
attachments: [
@@ -60,10 +60,10 @@ describe('normalizeMessagePreview', () => {
});
const result = normalizeMessagePreview(message, mockT);
- expect(result).toBe('Attachment title');
+ expect(result).toBe('Attachment description');
});
- it('should return attachment title when message is not provided', () => {
+ it('should return attachment title when description is not available', () => {
const message = createFakeMessageWithAttachment({
msg: '',
attachments: [
@@ -112,7 +112,7 @@ describe('normalizeMessagePreview', () => {
expect(result).toBe('Second attachment title');
});
- it('should find first attachment title', () => {
+ it('should find first attachment description', () => {
const message = createFakeMessageWithAttachment({
msg: '',
attachments: [
@@ -129,7 +129,21 @@ describe('normalizeMessagePreview', () => {
});
const result = normalizeMessagePreview(message, mockT);
- expect(result).toBe('Third attachment title');
+ expect(result).toBe('Second attachment description');
+ });
+
+ it('should escape HTML in attachment description', () => {
+ const message = createFakeMessageWithAttachment({
+ msg: '',
+ attachments: [
+ {
+ description: '',
+ },
+ ],
+ });
+ const result = normalizeMessagePreview(message, mockT);
+
+ expect(result).toBe('<script>alert("xss")</script>');
});
it('should escape HTML in attachment title', () => {
diff --git a/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.ts b/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.ts
index 0fbefea5fa48d..53bf5bf2d4056 100644
--- a/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.ts
+++ b/apps/meteor/client/lib/utils/normalizeMessagePreview/normalizeMessagePreview.ts
@@ -11,7 +11,11 @@ export const normalizeMessagePreview = (message: IMessage, t: TFunction): string
}
if (message.attachments) {
- const attachment = message.attachments.find((attachment) => attachment.title);
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
+
+ if (attachment?.description) {
+ return escapeHTML(attachment.description);
+ }
if (attachment?.title) {
return escapeHTML(attachment.title);
diff --git a/apps/meteor/client/navbar/NavBarOmnichannelGroup/hooks/useOmnichannelLivechatToggle.ts b/apps/meteor/client/navbar/NavBarOmnichannelGroup/hooks/useOmnichannelLivechatToggle.ts
index 89a46c6eab2bc..372c53d50890a 100644
--- a/apps/meteor/client/navbar/NavBarOmnichannelGroup/hooks/useOmnichannelLivechatToggle.ts
+++ b/apps/meteor/client/navbar/NavBarOmnichannelGroup/hooks/useOmnichannelLivechatToggle.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { Keys } from '@rocket.chat/icons';
import { useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -11,7 +11,7 @@ export const useOmnichannelLivechatToggle = () => {
const changeAgentStatus = useEndpoint('POST', '/v1/livechat/agent.status');
const dispatchToastMessage = useToastMessageDispatch();
- const handleAvailableStatusChange = useEffectEvent(async () => {
+ const handleAvailableStatusChange = useStableCallback(async () => {
try {
await changeAgentStatus({});
} catch (error: unknown) {
diff --git a/apps/meteor/client/navbar/NavBarPagesGroup/NavBarItemDirectoryPage.tsx b/apps/meteor/client/navbar/NavBarPagesGroup/NavBarItemDirectoryPage.tsx
index 3b28f53d4282b..65f1b6edee44d 100644
--- a/apps/meteor/client/navbar/NavBarPagesGroup/NavBarItemDirectoryPage.tsx
+++ b/apps/meteor/client/navbar/NavBarPagesGroup/NavBarItemDirectoryPage.tsx
@@ -1,5 +1,5 @@
import { NavBarItem } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRouter, useCurrentRoutePath } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes } from 'react';
@@ -7,7 +7,7 @@ type NavBarItemDirectoryPageProps = Omit, 'is'>;
const NavBarItemDirectoryPage = (props: NavBarItemDirectoryPageProps) => {
const router = useRouter();
- const handleDirectory = useEffectEvent(() => {
+ const handleDirectory = useStableCallback(() => {
router.navigate('/directory');
});
const currentRoute = useCurrentRoutePath();
diff --git a/apps/meteor/client/navbar/NavBarPagesGroup/NavBarItemHomePage.tsx b/apps/meteor/client/navbar/NavBarPagesGroup/NavBarItemHomePage.tsx
index 061dbf10c3c8a..920e424a6c43e 100644
--- a/apps/meteor/client/navbar/NavBarPagesGroup/NavBarItemHomePage.tsx
+++ b/apps/meteor/client/navbar/NavBarPagesGroup/NavBarItemHomePage.tsx
@@ -1,5 +1,5 @@
import { NavBarItem } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRouter, useLayout, useSetting, useCurrentRoutePath } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes } from 'react';
@@ -9,7 +9,7 @@ const NavBarItemHomePage = (props: NavBarItemHomePageProps) => {
const router = useRouter();
const { sidebar } = useLayout();
const showHome = useSetting('Layout_Show_Home_Button');
- const handleHome = useEffectEvent(() => {
+ const handleHome = useStableCallback(() => {
sidebar.toggle();
router.navigate('/home');
});
diff --git a/apps/meteor/client/navbar/NavBarPagesGroup/NavBarPagesStackMenu.tsx b/apps/meteor/client/navbar/NavBarPagesGroup/NavBarPagesStackMenu.tsx
index 16e7b81e4378e..05180536d360e 100644
--- a/apps/meteor/client/navbar/NavBarPagesGroup/NavBarPagesStackMenu.tsx
+++ b/apps/meteor/client/navbar/NavBarPagesGroup/NavBarPagesStackMenu.tsx
@@ -1,5 +1,5 @@
import { NavBarItem } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { GenericMenu } from '@rocket.chat/ui-client';
import { useCurrentRoutePath, useLayout, useRouter, useSetting } from '@rocket.chat/ui-contexts';
@@ -15,7 +15,7 @@ const NavBarPagesStackMenu = (props: NavBarPagesStackMenuProps) => {
const { sidebar } = useLayout();
const router = useRouter();
- const handleGoToHome = useEffectEvent(() => {
+ const handleGoToHome = useStableCallback(() => {
sidebar.toggle();
router.navigate('/home');
});
diff --git a/apps/meteor/client/navbar/NavBarPagesGroup/hooks/useCreateRoomModal.tsx b/apps/meteor/client/navbar/NavBarPagesGroup/hooks/useCreateRoomModal.tsx
index d1af9eb355990..8b602a6a639ba 100644
--- a/apps/meteor/client/navbar/NavBarPagesGroup/hooks/useCreateRoomModal.tsx
+++ b/apps/meteor/client/navbar/NavBarPagesGroup/hooks/useCreateRoomModal.tsx
@@ -1,11 +1,11 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
export const useCreateRoomModal = (Component: FC): (() => void) => {
const setModal = useSetModal();
- return useEffectEvent(() => {
+ return useStableCallback(() => {
const handleClose = (): void => {
setModal(null);
};
diff --git a/apps/meteor/client/navbar/NavBarSearch/NavBarSearch.tsx b/apps/meteor/client/navbar/NavBarSearch/NavBarSearch.tsx
index 9fc12fafa368f..82dcfecf2af41 100644
--- a/apps/meteor/client/navbar/NavBarSearch/NavBarSearch.tsx
+++ b/apps/meteor/client/navbar/NavBarSearch/NavBarSearch.tsx
@@ -2,7 +2,7 @@ import { useFocusManager } from '@react-aria/focus';
import { useOverlayTrigger } from '@react-aria/overlays';
import { useOverlayTriggerState } from '@react-stately/overlays';
import { Box, Icon, IconButton, TextInput } from '@rocket.chat/fuselage';
-import { useEffectEvent, useMergedRefs } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useMergedRefs } from '@rocket.chat/fuselage-hooks';
import { useCallback, useEffect, useRef } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
@@ -47,7 +47,7 @@ const NavBarSearch = () => {
state.close();
}, [resetField, state]);
- const handleClearText = useEffectEvent(() => {
+ const handleClearText = useStableCallback(() => {
resetField('filterText');
setFocus('filterText');
});
diff --git a/apps/meteor/client/navbar/NavBarSearch/NavBarSearchListbox.tsx b/apps/meteor/client/navbar/NavBarSearch/NavBarSearchListbox.tsx
index 481081a82e14a..1b3ef415fa414 100644
--- a/apps/meteor/client/navbar/NavBarSearch/NavBarSearchListbox.tsx
+++ b/apps/meteor/client/navbar/NavBarSearch/NavBarSearchListbox.tsx
@@ -1,7 +1,7 @@
import type { OverlayTriggerAria } from '@react-aria/overlays';
import type { OverlayTriggerState } from '@react-stately/overlays';
import { Box, Tile } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent, useOutsideClick } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback, useOutsideClick } from '@rocket.chat/fuselage-hooks';
import { CustomScrollbars } from '@rocket.chat/ui-client';
import { useRef } from 'react';
import { useFormContext } from 'react-hook-form';
@@ -30,7 +30,7 @@ const NavBarSearchListBox = ({ state, overlayProps }: NavBarSearchListBoxProps)
const debouncedFilter = useDebouncedValue(filterText, 500);
- const handleSelect = useEffectEvent(() => {
+ const handleSelect = useStableCallback(() => {
state.close();
resetField('filterText');
});
diff --git a/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/EditStatusModal.tsx b/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/EditStatusModal.tsx
index e5cc289312bda..5616da39011d1 100644
--- a/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/EditStatusModal.tsx
+++ b/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/EditStatusModal.tsx
@@ -18,9 +18,9 @@ import {
ModalFooter,
ModalFooterControllers,
} from '@rocket.chat/fuselage';
-import { useEffectEvent, useLocalStorage } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useLocalStorage } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useSetting, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
-import type { ChangeEvent, ComponentProps, FormEvent } from 'react';
+import type { ChangeEvent, ComponentProps } from 'react';
import { useState, useCallback, useId } from 'react';
import UserStatusMenu from '../../../components/UserStatusMenu';
@@ -46,7 +46,7 @@ const EditStatusModal = ({ onClose, userStatus, userStatusText }: EditStatusModa
const setUserStatus = useEndpoint('POST', '/v1/users.setStatus');
- const handleStatusText = useEffectEvent((e: ChangeEvent): void => {
+ const handleStatusText = useStableCallback((e: ChangeEvent): void => {
setStatusText(e.currentTarget.value);
if (statusText && statusText.length > USER_STATUS_TEXT_MAX_LENGTH) {
@@ -76,7 +76,7 @@ const EditStatusModal = ({ onClose, userStatus, userStatusText }: EditStatusModa
wrapperFunction={(props: ComponentProps) => (
{
+ onSubmit={(e) => {
e.preventDefault();
handleSaveStatus();
}}
diff --git a/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx b/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx
index 99100fdad2b56..21dfafd684eae 100644
--- a/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx
+++ b/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx
@@ -1,5 +1,5 @@
import { Badge } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { defaultFeaturesPreview, usePreferenceFeaturePreviewList } from '@rocket.chat/ui-client';
import { useRouter } from '@rocket.chat/ui-contexts';
@@ -11,16 +11,16 @@ export const useAccountItems = (): GenericMenuItemProps[] => {
const { unseenFeatures, featurePreviewEnabled } = usePreferenceFeaturePreviewList();
- const handleMyAccount = useEffectEvent(() => {
+ const handleMyAccount = useStableCallback(() => {
router.navigate('/account');
});
- const handlePreferences = useEffectEvent(() => {
+ const handlePreferences = useStableCallback(() => {
router.navigate('/account/preferences');
});
- const handleFeaturePreview = useEffectEvent(() => {
+ const handleFeaturePreview = useStableCallback(() => {
router.navigate('/account/feature-preview');
});
- const handleAccessibility = useEffectEvent(() => {
+ const handleAccessibility = useStableCallback(() => {
router.navigate('/account/accessibility-and-appearance');
});
diff --git a/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/hooks/useUserMenu.tsx b/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/hooks/useUserMenu.tsx
index c2e16f22cb1a1..b29aafb769842 100644
--- a/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/hooks/useUserMenu.tsx
+++ b/apps/meteor/client/navbar/NavBarSettingsToolbar/UserMenu/hooks/useUserMenu.tsx
@@ -1,5 +1,5 @@
import type { IUser } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { useLogout } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -19,7 +19,7 @@ export const useUserMenu = (user: IUser) => {
const handleKeyboardShortcuts = useKeyboardShortcutsModalHandler();
const logout = useLogout();
- const handleLogout = useEffectEvent(() => {
+ const handleLogout = useStableCallback(() => {
logout();
});
diff --git a/apps/meteor/client/providers/CustomSoundProvider/CustomSoundProvider.tsx b/apps/meteor/client/providers/CustomSoundProvider/CustomSoundProvider.tsx
index 4dfcedaa1752f..9cda0dc4cb08f 100644
--- a/apps/meteor/client/providers/CustomSoundProvider/CustomSoundProvider.tsx
+++ b/apps/meteor/client/providers/CustomSoundProvider/CustomSoundProvider.tsx
@@ -1,5 +1,5 @@
import type { ICustomSound } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { CustomSoundContext, useStream, useUserPreference } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect, useMemo, useRef, type ReactNode } from 'react';
@@ -34,7 +34,7 @@ const CustomSoundProvider = ({ children }: CustomSoundProviderProps) => {
initialData: defaultSounds,
});
- const play = useEffectEvent((soundId: ICustomSound['_id'], { volume = 1, loop = false } = {}) => {
+ const play = useStableCallback((soundId: ICustomSound['_id'], { volume = 1, loop = false } = {}) => {
stop(soundId);
const item = list?.find(({ _id }) => _id === soundId);
@@ -56,7 +56,7 @@ const CustomSoundProvider = ({ children }: CustomSoundProviderProps) => {
};
});
- const pause = useEffectEvent((soundId: ICustomSound['_id']) => {
+ const pause = useStableCallback((soundId: ICustomSound['_id']) => {
const current = audioRefs.current?.find(({ id }) => id === soundId);
if (current) {
current.pause();
@@ -64,7 +64,7 @@ const CustomSoundProvider = ({ children }: CustomSoundProviderProps) => {
}
});
- const stop = useEffectEvent((soundId: ICustomSound['_id']) => {
+ const stop = useStableCallback((soundId: ICustomSound['_id']) => {
const current = audioRefs.current?.find(({ id }) => id === soundId);
if (current) {
current.load();
diff --git a/apps/meteor/client/providers/DeviceProvider/DeviceProvider.tsx b/apps/meteor/client/providers/DeviceProvider/DeviceProvider.tsx
index 686931813ce61..c6411d78e3d13 100644
--- a/apps/meteor/client/providers/DeviceProvider/DeviceProvider.tsx
+++ b/apps/meteor/client/providers/DeviceProvider/DeviceProvider.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { Device, DeviceContextValue } from '@rocket.chat/ui-contexts';
import { DeviceContext } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient, keepPreviousData } from '@tanstack/react-query';
@@ -40,7 +40,7 @@ export const DeviceProvider = ({ children }: DeviceProviderProps) => {
setSelectedAudioInputDevice(device);
};
- const setAudioOutputDevice = useEffectEvent(
+ const setAudioOutputDevice = useStableCallback(
({ outputDevice, HTMLAudioElement }: { outputDevice: Device; HTMLAudioElement: HTMLAudioElement }): void => {
if (!isSetSinkIdAvailable()) {
throw new Error('setSinkId is not available in this browser');
diff --git a/apps/meteor/client/providers/EmojiPickerProvider/EmojiPickerProvider.tsx b/apps/meteor/client/providers/EmojiPickerProvider/EmojiPickerProvider.tsx
index 1de2c278d65b0..64538ef28df8c 100644
--- a/apps/meteor/client/providers/EmojiPickerProvider/EmojiPickerProvider.tsx
+++ b/apps/meteor/client/providers/EmojiPickerProvider/EmojiPickerProvider.tsx
@@ -1,4 +1,4 @@
-import { useDebouncedState, useEffectEvent, useLocalStorage } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedState, useStableCallback, useLocalStorage } from '@rocket.chat/fuselage-hooks';
import type { ReactNode, ContextType } from 'react';
import { useState, useCallback, useMemo, useSyncExternalStore } from 'react';
@@ -27,7 +27,7 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }) => {
getFrequentEmoji(frequentEmojis.map(([emoji]) => emoji)),
);
- const setQuickReactions = useEffectEvent(() => _setQuickReactions(getFrequentEmoji(frequentEmojis.map(([emoji]) => emoji))));
+ const setQuickReactions = useStableCallback(() => _setQuickReactions(getFrequentEmoji(frequentEmojis.map(([emoji]) => emoji))));
const [sub, getSnapshot] = useMemo(() => {
return createEmojiListByCategorySubscription(customItemsLimit, actualTone, recentEmojis, setRecentEmojis, setQuickReactions);
}, [customItemsLimit, actualTone, recentEmojis, setRecentEmojis, setQuickReactions]);
diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationManageServerModal.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationManageServerModal.tsx
index 2baaeac8753bd..c96b5d966bb5f 100644
--- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationManageServerModal.tsx
+++ b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationManageServerModal.tsx
@@ -18,7 +18,7 @@ import {
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useSetModal, useTranslation, useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useMutation, useQueryClient } from '@tanstack/react-query';
-import type { FormEvent } from 'react';
+import type { ChangeEvent } from 'react';
import { useState } from 'react';
import MatrixFederationRemoveServerList from './MatrixFederationRemoveServerList';
@@ -90,7 +90,7 @@ const MatrixFederationAddServerModal = ({ onClickClose }: MatrixFederationAddSer
) => {
+ onChange={(e: ChangeEvent) => {
setServerName(e.currentTarget.value);
if (errorKey) {
setErrorKey(undefined);
diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx
index 853df819ddbb5..ff5f5ab94fc15 100644
--- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx
+++ b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx
@@ -2,7 +2,7 @@ import type { SelectOption } from '@rocket.chat/fuselage';
import { Box, Select, TextInput } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import { useSetModal } from '@rocket.chat/ui-contexts';
-import type { FormEvent } from 'react';
+import type { ChangeEvent } from 'react';
import { useCallback, useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -53,7 +53,7 @@ const MatrixFederationSearchModalContent = ({ defaultSelectedServer, servers }:
flexGrow={4}
flexShrink={0}
value={roomName}
- onChange={(e: FormEvent) => setRoomName(e.currentTarget.value)}
+ onChange={(e: ChangeEvent) => setRoomName(e.currentTarget.value)}
/>
diff --git a/apps/meteor/client/uikit/hooks/useMessageBlockContextValue.ts b/apps/meteor/client/uikit/hooks/useMessageBlockContextValue.ts
index f70c06d8ee13f..0fe1ae17256e4 100644
--- a/apps/meteor/client/uikit/hooks/useMessageBlockContextValue.ts
+++ b/apps/meteor/client/uikit/hooks/useMessageBlockContextValue.ts
@@ -1,5 +1,5 @@
import type { IRoom, IMessage } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { UiKitContext } from '@rocket.chat/fuselage-ui-kit';
import { useRoomToolbox } from '@rocket.chat/ui-contexts';
import {
@@ -24,7 +24,7 @@ export const useMessageBlockContextValue = (rid: IRoom['_id'], mid: IMessage['_i
const dispatchPopup = useVideoConfDispatchOutgoing();
const loadVideoConfCapabilities = useVideoConfLoadCapabilities();
- const handleOpenVideoConf = useEffectEvent(async (rid: IRoom['_id']) => {
+ const handleOpenVideoConf = useStableCallback(async (rid: IRoom['_id']) => {
if (isCalling || isRinging) {
return;
}
diff --git a/apps/meteor/client/views/account/security/TwoFactorEmail.tsx b/apps/meteor/client/views/account/security/TwoFactorEmail.tsx
index 17c261e8df5a0..5929662999cb2 100644
--- a/apps/meteor/client/views/account/security/TwoFactorEmail.tsx
+++ b/apps/meteor/client/views/account/security/TwoFactorEmail.tsx
@@ -1,6 +1,6 @@
import { Box, Field, FieldLabel, FieldRow, Margins, ToggleSwitch } from '@rocket.chat/fuselage';
import { useToastMessageDispatch, useUser } from '@rocket.chat/ui-contexts';
-import type { ComponentProps, FormEvent } from 'react';
+import type { ComponentProps, ChangeEvent } from 'react';
import { useCallback, useId } from 'react';
import { useTranslation } from 'react-i18next';
@@ -27,7 +27,7 @@ const TwoFactorEmail = (props: ComponentProps) => {
});
const handleEnable = useCallback(
- async (e: FormEvent) => {
+ async (e: ChangeEvent) => {
if (e.currentTarget.checked) {
await enable2faAction();
} else {
diff --git a/apps/meteor/client/views/account/security/TwoFactorTOTP.tsx b/apps/meteor/client/views/account/security/TwoFactorTOTP.tsx
index 26c51e32702b5..31d56dc2a4d20 100644
--- a/apps/meteor/client/views/account/security/TwoFactorTOTP.tsx
+++ b/apps/meteor/client/views/account/security/TwoFactorTOTP.tsx
@@ -1,7 +1,7 @@
import { Box, Button, TextInput, Margins, Field, FieldRow, FieldLabel, ToggleSwitch } from '@rocket.chat/fuselage';
-import { useEffectEvent, useSafely } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useSafely } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useUser, useMethod } from '@rocket.chat/ui-contexts';
-import type { ComponentPropsWithoutRef, FormEvent } from 'react';
+import type { ComponentPropsWithoutRef, ChangeEvent } from 'react';
import { useState, useCallback, useEffect, useId } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
@@ -51,7 +51,7 @@ const TwoFactorTOTP = (props: TwoFactorTOTPProps) => {
updateCodesRemaining();
}, [checkCodesRemainingFn, setCodesRemaining, totpEnabled]);
- const enableTotp = useEffectEvent(async () => {
+ const enableTotp = useStableCallback(async () => {
try {
const result = await enableTotpFn();
@@ -64,7 +64,7 @@ const TwoFactorTOTP = (props: TwoFactorTOTPProps) => {
}
});
- const disableTotp = useEffectEvent(async () => {
+ const disableTotp = useStableCallback(async () => {
if (!totpEnabled) {
setRegisteringTotp(false);
@@ -92,7 +92,7 @@ const TwoFactorTOTP = (props: TwoFactorTOTPProps) => {
setModal();
});
- const handleToggleTotp = useEffectEvent(async (e: FormEvent) => {
+ const handleToggleTotp = useStableCallback(async (e: ChangeEvent) => {
if (e.currentTarget?.checked) {
void enableTotp();
} else {
diff --git a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.tsx b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.tsx
index a9b067e52fe61..f6e08aeb530f3 100644
--- a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.tsx
+++ b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesForm.tsx
@@ -10,7 +10,7 @@ import {
IconButton,
TextInput,
} from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarScrollableContent } from '@rocket.chat/ui-client';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useCallback, useId, useMemo, Fragment, useState } from 'react';
@@ -74,7 +74,7 @@ const AttributesForm = ({ onSave, onCancel, description }: AttributesFormProps)
const [showDisclaimer, setShowDisclaimer] = useState([]);
const viewRoomsAction = useViewRoomsAction();
- const removeLockedAttribute = useEffectEvent(async (index: number) => {
+ const removeLockedAttribute = useStableCallback(async (index: number) => {
const isInUse = await isAttributeUsed();
if (showDisclaimer.includes(index)) {
return;
diff --git a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesPage.tsx b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesPage.tsx
index 23401eb150b61..9838e5d1811ef 100644
--- a/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesPage.tsx
+++ b/apps/meteor/client/views/admin/ABAC/ABACAttributesTab/AttributesPage.tsx
@@ -1,5 +1,5 @@
import { Box, Button, Icon, Margins, Pagination, TextInput } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableBody,
@@ -31,7 +31,7 @@ const AttributesPage = () => {
const isABACAvailable = useIsABACAvailable();
const router = useRouter();
- const handleNewAttribute = useEffectEvent(() => {
+ const handleNewAttribute = useStableCallback(() => {
router.navigate({
name: 'admin-ABAC',
params: {
diff --git a/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomForm.tsx b/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomForm.tsx
index e08218bd40376..854919a86b153 100644
--- a/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomForm.tsx
+++ b/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomForm.tsx
@@ -1,5 +1,5 @@
import { Box, Callout, Field, FieldLabel, FieldRow, FieldError, ButtonGroup, Button, ContextualbarFooter } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal, ContextualbarScrollableContent } from '@rocket.chat/ui-client';
import { useSetModal } from '@rocket.chat/ui-contexts';
import type { Dispatch, SetStateAction } from 'react';
@@ -42,7 +42,7 @@ const RoomForm = ({ onClose, onSave, roomInfo, setSelectedRoomLabel, redacted =
const setModal = useSetModal();
- const updateAction = useEffectEvent(async (action: () => void) => {
+ const updateAction = useStableCallback(async (action: () => void) => {
setModal(
{
+ const handleSave = useStableCallback(() => {
if (roomInfo) {
updateAction(handleSubmit(onSave));
} else {
diff --git a/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomsPage.tsx b/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomsPage.tsx
index dc627292ca31f..bfcb2df9b3900 100644
--- a/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomsPage.tsx
+++ b/apps/meteor/client/views/admin/ABAC/ABACRoomsTab/RoomsPage.tsx
@@ -1,5 +1,5 @@
import { Box, Button, Icon, Margins, Pagination, Select, TextInput } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableBody,
@@ -36,7 +36,7 @@ const RoomsPage = () => {
const isABACAvailable = useIsABACAvailable();
const isExternalStore = useIsExternalAttributeStore();
- const handleNewAttribute = useEffectEvent(() => {
+ const handleNewAttribute = useStableCallback(() => {
router.navigate({
name: 'admin-ABAC',
params: {
diff --git a/apps/meteor/client/views/admin/ABAC/AdminABACPage.tsx b/apps/meteor/client/views/admin/ABAC/AdminABACPage.tsx
index a791f07a01d37..46f97254495ff 100644
--- a/apps/meteor/client/views/admin/ABAC/AdminABACPage.tsx
+++ b/apps/meteor/client/views/admin/ABAC/AdminABACPage.tsx
@@ -1,5 +1,5 @@
import { Box, Button, ButtonGroup, Callout } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarDialog, Page, PageContent, PageHeader } from '@rocket.chat/ui-client';
import { useSetting, useRouteParameter, useRouter } from '@rocket.chat/ui-contexts';
import { Trans, useTranslation } from 'react-i18next';
@@ -37,7 +37,7 @@ const AdminABACPage = ({ shouldShowWarning }: AdminABACPageProps) => {
const isSyncDisabled = !ldapEnabled || !abacEnabled;
const tabPermissions = useABACTabPermissions();
- const handleCloseContextualbar = useEffectEvent((): void => {
+ const handleCloseContextualbar = useStableCallback((): void => {
if (!context) {
return;
}
diff --git a/apps/meteor/client/views/admin/ABAC/hooks/useAttributeOptions.tsx b/apps/meteor/client/views/admin/ABAC/hooks/useAttributeOptions.tsx
index dc1dcf3101883..c0186a0bb8e78 100644
--- a/apps/meteor/client/views/admin/ABAC/hooks/useAttributeOptions.tsx
+++ b/apps/meteor/client/views/admin/ABAC/hooks/useAttributeOptions.tsx
@@ -1,5 +1,5 @@
import { Box } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { GenericModal } from '@rocket.chat/ui-client';
import { useRouter, useSetModal, useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
@@ -21,7 +21,7 @@ export const useAttributeOptions = (attribute: { _id: string; key: string }): Ge
const isABACAvailable = useIsABACAvailable();
const viewRoomsAction = useViewRoomsAction();
- const editAction = useEffectEvent(() => {
+ const editAction = useStableCallback(() => {
return router.navigate(
{
name: 'admin-ABAC',
@@ -49,7 +49,7 @@ export const useAttributeOptions = (attribute: { _id: string; key: string }): Ge
},
});
- const deleteAction = useEffectEvent(async () => {
+ const deleteAction = useStableCallback(async () => {
const isUsed = await isAttributeUsed();
if (isUsed.inUse) {
return setModal(
diff --git a/apps/meteor/client/views/admin/ABAC/hooks/useRoomItems.tsx b/apps/meteor/client/views/admin/ABAC/hooks/useRoomItems.tsx
index da0a91fe390de..05883606b0a07 100644
--- a/apps/meteor/client/views/admin/ABAC/hooks/useRoomItems.tsx
+++ b/apps/meteor/client/views/admin/ABAC/hooks/useRoomItems.tsx
@@ -1,5 +1,5 @@
import { Box } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { useRouter } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -13,7 +13,7 @@ export const useRoomItems = (room: { rid: string; name: string }): GenericMenuIt
const setDeleteRoomModal = useDeleteRoomModal(room);
const isABACAvailable = useIsABACAvailable();
- const editAction = useEffectEvent(() => {
+ const editAction = useStableCallback(() => {
return router.navigate(
{
name: 'admin-ABAC',
diff --git a/apps/meteor/client/views/admin/ABAC/hooks/useViewRoomsAction.ts b/apps/meteor/client/views/admin/ABAC/hooks/useViewRoomsAction.ts
index 7edcbbbc97c89..d5faedfc3cfb1 100644
--- a/apps/meteor/client/views/admin/ABAC/hooks/useViewRoomsAction.ts
+++ b/apps/meteor/client/views/admin/ABAC/hooks/useViewRoomsAction.ts
@@ -1,9 +1,9 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRouter } from '@rocket.chat/ui-contexts';
export const useViewRoomsAction = () => {
const router = useRouter();
- return useEffectEvent((key: string) => {
+ return useStableCallback((key: string) => {
return router.navigate(
{
name: 'admin-ABAC',
diff --git a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
index 187b4fb317bd5..e35ac9df9db23 100644
--- a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
+++ b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
@@ -2,7 +2,7 @@ import { Field, FieldLabel, FieldRow, TextInput, Box, Margins, Button, ButtonGro
import { ContextualbarScrollableContent, ContextualbarFooter } from '@rocket.chat/ui-client';
import { useToastMessageDispatch, type UploadResult } from '@rocket.chat/ui-contexts';
import fileSize from 'filesize';
-import type { FormEvent } from 'react';
+import type { ChangeEvent } from 'react';
import { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -86,7 +86,7 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr
): void => setName(e.currentTarget.value)}
+ onChange={(e: ChangeEvent): void => setName(e.currentTarget.value)}
placeholder={t('Name')}
/>
diff --git a/apps/meteor/client/views/admin/customSounds/EditSound.tsx b/apps/meteor/client/views/admin/customSounds/EditSound.tsx
index 54c338e444362..9fa3043b80ee8 100644
--- a/apps/meteor/client/views/admin/customSounds/EditSound.tsx
+++ b/apps/meteor/client/views/admin/customSounds/EditSound.tsx
@@ -2,7 +2,7 @@ import { Box, Button, ButtonGroup, Margins, TextInput, Field, FieldLabel, FieldR
import { GenericModal, ContextualbarScrollableContent, ContextualbarFooter } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts';
import fileSize from 'filesize';
-import type { SyntheticEvent } from 'react';
+import type { ChangeEvent } from 'react';
import { useCallback, useState, useMemo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
@@ -117,7 +117,7 @@ function EditSound({ close, onChange, data, ...props }: EditSoundProps) {
): void => setName(e.currentTarget.value)}
+ onChange={(e: ChangeEvent): void => setName(e.currentTarget.value)}
placeholder={t('Name')}
/>
diff --git a/apps/meteor/client/views/admin/customUserStatus/hooks/useStatusDisabledModal.tsx b/apps/meteor/client/views/admin/customUserStatus/hooks/useStatusDisabledModal.tsx
index 7cb32da3cd29a..4847051fc89e0 100644
--- a/apps/meteor/client/views/admin/customUserStatus/hooks/useStatusDisabledModal.tsx
+++ b/apps/meteor/client/views/admin/customUserStatus/hooks/useStatusDisabledModal.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRole, useRoute, useSetModal } from '@rocket.chat/ui-contexts';
import CustomUserStatusDisabledModal from '../CustomUserStatusDisabledModal';
@@ -6,8 +6,8 @@ import CustomUserStatusDisabledModal from '../CustomUserStatusDisabledModal';
export const useStatusDisabledModal = () => {
const userStatusRoute = useRoute('user-status');
const setModal = useSetModal();
- const closeModal = useEffectEvent(() => setModal());
- const handleGoToSettings = useEffectEvent(() => {
+ const closeModal = useStableCallback(() => setModal());
+ const handleGoToSettings = useStableCallback(() => {
userStatusRoute.push({ context: 'presence-service' });
closeModal();
});
diff --git a/apps/meteor/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminRow.tsx b/apps/meteor/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminRow.tsx
index da0733645f0cc..70eb894b2d185 100644
--- a/apps/meteor/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminRow.tsx
+++ b/apps/meteor/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminRow.tsx
@@ -1,5 +1,5 @@
import { Box } from '@rocket.chat/fuselage';
-import { useMediaQuery, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useMediaQuery, useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { GenericMenu, GenericTableRow, GenericTableCell } from '@rocket.chat/ui-client';
import { useRoute } from '@rocket.chat/ui-contexts';
@@ -39,7 +39,7 @@ const DeviceManagementAdminRow = ({
const handleDeviceLogout = useDeviceLogout(_id, '/v1/sessions/logout');
- const handleClick = useEffectEvent((): void => {
+ const handleClick = useStableCallback((): void => {
deviceManagementRouter.push({
context: 'info',
id: _id,
diff --git a/apps/meteor/client/views/admin/emailInbox/EmailInboxForm.tsx b/apps/meteor/client/views/admin/emailInbox/EmailInboxForm.tsx
index 70c17b13fb3fc..37ef35b2e1dda 100644
--- a/apps/meteor/client/views/admin/emailInbox/EmailInboxForm.tsx
+++ b/apps/meteor/client/views/admin/emailInbox/EmailInboxForm.tsx
@@ -18,7 +18,7 @@ import {
FieldError,
FieldHint,
} from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { validateEmail } from '@rocket.chat/tools';
import { GenericModal, PageScrollableContentWithShadow } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useRoute, useEndpoint } from '@rocket.chat/ui-contexts';
@@ -93,7 +93,7 @@ const EmailInboxForm = ({ inboxData }: EmailInboxFormProps) => {
mode: 'all',
});
- const handleDelete = useEffectEvent(() => {
+ const handleDelete = useStableCallback(() => {
const deleteInbox = async (): Promise => {
try {
await deleteInboxAction();
@@ -113,7 +113,7 @@ const EmailInboxForm = ({ inboxData }: EmailInboxFormProps) => {
);
});
- const handleSave = useEffectEvent(
+ const handleSave = useStableCallback(
async ({
active,
name,
@@ -172,7 +172,7 @@ const EmailInboxForm = ({ inboxData }: EmailInboxFormProps) => {
},
);
- const checkEmailExists = useEffectEvent(async (email: string) => {
+ const checkEmailExists = useStableCallback(async (email: string) => {
if (!email) {
return;
}
diff --git a/apps/meteor/client/views/admin/import/ImportProgressPage.tsx b/apps/meteor/client/views/admin/import/ImportProgressPage.tsx
index 2f80739b4b8d1..3a7cbf4df3552 100644
--- a/apps/meteor/client/views/admin/import/ImportProgressPage.tsx
+++ b/apps/meteor/client/views/admin/import/ImportProgressPage.tsx
@@ -1,6 +1,6 @@
import type { ProgressStep } from '@rocket.chat/core-typings';
import { Box, Margins, ProgressBar, Throbber } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { Page, PageHeader, PageScrollableContentWithShadow } from '@rocket.chat/ui-client';
import { useToastMessageDispatch, useEndpoint, useStream, useRouter } from '@rocket.chat/ui-contexts';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
@@ -43,7 +43,7 @@ const ImportProgressPage = function ImportProgressPage() {
refetchInterval: 1000,
});
- const handleProgressUpdated = useEffectEvent(
+ const handleProgressUpdated = useStableCallback(
({ key, step, completed, total }: { key: string; step: ProgressStep; completed: number; total: number }) => {
if (!currentOperation.isSuccess) {
return;
diff --git a/apps/meteor/client/views/admin/import/NewImportPage.tsx b/apps/meteor/client/views/admin/import/NewImportPage.tsx
index 8cd03322ed509..7fcc67e42cc33 100644
--- a/apps/meteor/client/views/admin/import/NewImportPage.tsx
+++ b/apps/meteor/client/views/admin/import/NewImportPage.tsx
@@ -19,7 +19,7 @@ import { Page, PageHeader, PageScrollableContentWithShadow } from '@rocket.chat/
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useToastMessageDispatch, useRouter, useRouteParameter, useSetting, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
-import type { ChangeEvent, DragEvent, FormEvent, Key, SyntheticEvent } from 'react';
+import type { ChangeEvent, DragEvent, Key, SyntheticEvent } from 'react';
import { useState, useMemo, useEffect, useId } from 'react';
import { useTranslation } from 'react-i18next';
@@ -146,7 +146,7 @@ function NewImportPage() {
const [fileUrl, setFileUrl] = useSafely(useState(''));
- const handleFileUrlChange = (event: FormEvent) => {
+ const handleFileUrlChange = (event: ChangeEvent) => {
setFileUrl(event.currentTarget.value);
};
@@ -170,7 +170,7 @@ function NewImportPage() {
const [filePath, setFilePath] = useSafely(useState(''));
- const handleFilePathChange = (event: FormEvent) => {
+ const handleFilePathChange = (event: ChangeEvent) => {
setFilePath(event.currentTarget.value);
};
diff --git a/apps/meteor/client/views/admin/import/useErrorHandler.ts b/apps/meteor/client/views/admin/import/useErrorHandler.ts
index ce0f9e81f8736..5befa13c6103d 100644
--- a/apps/meteor/client/views/admin/import/useErrorHandler.ts
+++ b/apps/meteor/client/views/admin/import/useErrorHandler.ts
@@ -1,10 +1,10 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
export const useErrorHandler = () => {
const dispatchToastMessage = useToastMessageDispatch();
- return useEffectEvent((error: unknown, defaultMessage?: unknown) => {
+ return useStableCallback((error: unknown, defaultMessage?: unknown) => {
console.error(error);
dispatchToastMessage({ type: 'error', message: error ?? defaultMessage });
diff --git a/apps/meteor/client/views/admin/integrations/outgoing/history/HistoryItem.tsx b/apps/meteor/client/views/admin/integrations/outgoing/history/HistoryItem.tsx
index 74327d4968bf3..513f60429469b 100644
--- a/apps/meteor/client/views/admin/integrations/outgoing/history/HistoryItem.tsx
+++ b/apps/meteor/client/views/admin/integrations/outgoing/history/HistoryItem.tsx
@@ -1,6 +1,6 @@
import type { IIntegrationHistory, Serialized } from '@rocket.chat/core-typings';
import { Button, Icon, Box, AccordionItem, Field, FieldGroup, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useMethod } from '@rocket.chat/ui-contexts';
import DOMPurify from 'dompurify';
import type { MouseEvent } from 'react';
@@ -36,7 +36,7 @@ const HistoryItem = ({ data }: { data: Serialized }) => {
const createdAt = typeof _createdAt === 'string' ? _createdAt : (_createdAt as Date).toISOString();
const updatedAt = typeof _updatedAt === 'string' ? _updatedAt : (_updatedAt as Date).toISOString();
- const handleClickReplay = useEffectEvent((e: MouseEvent) => {
+ const handleClickReplay = useStableCallback((e: MouseEvent) => {
e.stopPropagation();
replayOutgoingIntegration({ integrationId, historyId: _id });
});
diff --git a/apps/meteor/client/views/admin/moderation/ModConsoleReportDetails.tsx b/apps/meteor/client/views/admin/moderation/ModConsoleReportDetails.tsx
index 490cff666bf53..92b4837a304bc 100644
--- a/apps/meteor/client/views/admin/moderation/ModConsoleReportDetails.tsx
+++ b/apps/meteor/client/views/admin/moderation/ModConsoleReportDetails.tsx
@@ -1,6 +1,6 @@
import type { IUser } from '@rocket.chat/core-typings';
import { Tabs, TabsItem, ContextualbarHeader, ContextualbarTitle } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarClose, ContextualbarDialog } from '@rocket.chat/ui-client';
import { useTranslation, useRouter, useRouteParameter } from '@rocket.chat/ui-contexts';
import { useState } from 'react';
@@ -21,7 +21,7 @@ const ModConsoleReportDetails = ({ userId, default: defaultTab, onRedirect }: Mo
const activeTab = useRouteParameter('tab');
- const handleCloseContextualbar = useEffectEvent(() => moderationRoute.navigate(`/admin/moderation/${activeTab}`, { replace: true }));
+ const handleCloseContextualbar = useStableCallback(() => moderationRoute.navigate(`/admin/moderation/${activeTab}`, { replace: true }));
return (
diff --git a/apps/meteor/client/views/admin/moderation/ModerationConsoleTable.tsx b/apps/meteor/client/views/admin/moderation/ModerationConsoleTable.tsx
index 51f646d7ab0af..03208f1ca154e 100644
--- a/apps/meteor/client/views/admin/moderation/ModerationConsoleTable.tsx
+++ b/apps/meteor/client/views/admin/moderation/ModerationConsoleTable.tsx
@@ -1,6 +1,6 @@
import type { IUser } from '@rocket.chat/core-typings';
import { Pagination } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useMediaQuery, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useMediaQuery, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableLoadingTable,
@@ -63,7 +63,7 @@ const ModerationConsoleTable = () => {
placeholderData: keepPreviousData,
});
- const handleClick = useEffectEvent((id: IUser['_id']): void => {
+ const handleClick = useStableCallback((id: IUser['_id']): void => {
router.navigate({
pattern: '/admin/moderation/:tab?/:context?/:id?',
params: {
diff --git a/apps/meteor/client/views/admin/moderation/UserMessages.tsx b/apps/meteor/client/views/admin/moderation/UserMessages.tsx
index b0b9d9b50edf0..d8db785def5c1 100644
--- a/apps/meteor/client/views/admin/moderation/UserMessages.tsx
+++ b/apps/meteor/client/views/admin/moderation/UserMessages.tsx
@@ -1,5 +1,5 @@
import { Box, Callout, Message, StatesAction, StatesActions, StatesIcon, StatesTitle } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarFooter } from '@rocket.chat/ui-client';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
@@ -32,7 +32,7 @@ const UserMessages = ({ userId, onRedirect }: { userId: string; onRedirect: (mid
},
});
- const handleChange = useEffectEvent(() => {
+ const handleChange = useStableCallback(() => {
reloadUserMessages();
});
diff --git a/apps/meteor/client/views/admin/moderation/UserReports/ModConsoleUsersTable.tsx b/apps/meteor/client/views/admin/moderation/UserReports/ModConsoleUsersTable.tsx
index 6a21d7d1ed85c..1b2491ca6d3b8 100644
--- a/apps/meteor/client/views/admin/moderation/UserReports/ModConsoleUsersTable.tsx
+++ b/apps/meteor/client/views/admin/moderation/UserReports/ModConsoleUsersTable.tsx
@@ -1,6 +1,6 @@
import type { IUser } from '@rocket.chat/core-typings';
import { Pagination, States, StatesAction, StatesActions, StatesIcon, StatesTitle } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useMediaQuery, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useMediaQuery, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableLoadingTable,
@@ -59,7 +59,7 @@ const ModConsoleUsersTable = () => {
placeholderData: keepPreviousData,
});
- const handleClick = useEffectEvent((id: IUser['_id']): void => {
+ const handleClick = useStableCallback((id: IUser['_id']): void => {
router.navigate({
pattern: '/admin/moderation/:tab?/:context?/:id?',
params: { tab: 'users', context: 'info', id },
diff --git a/apps/meteor/client/views/admin/moderation/helpers/DateRangePicker.tsx b/apps/meteor/client/views/admin/moderation/helpers/DateRangePicker.tsx
index 08419042dfebd..eda5a5abd43a3 100644
--- a/apps/meteor/client/views/admin/moderation/helpers/DateRangePicker.tsx
+++ b/apps/meteor/client/views/admin/moderation/helpers/DateRangePicker.tsx
@@ -1,5 +1,5 @@
import { Select, Box, type SelectOption } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { subDays, subMonths, startOfMonth, endOfMonth, format } from 'date-fns';
import type { Key } from 'react';
import { useMemo, useEffect } from 'react';
@@ -33,7 +33,7 @@ const getWeekRange = (daysToSubtractFromStart: number, daysToSubtractFromEnd: nu
const DateRangePicker = ({ onChange, defaultSelectedKey = 'alldates' }: DateRangePickerProps) => {
const { t } = useTranslation();
- const handleRange = useEffectEvent((range: { start: string; end: string }) => {
+ const handleRange = useStableCallback((range: { start: string; end: string }) => {
onChange(range);
});
@@ -48,7 +48,7 @@ const DateRangePicker = ({ onChange, defaultSelectedKey = 'alldates' }: DateRang
].map(([value, label]) => [value, label] as SelectOption);
}, [t]);
- const handleOptionClick = useEffectEvent((action: Key) => {
+ const handleOptionClick = useStableCallback((action: Key) => {
switch (action) {
case 'today':
handleRange(getWeekRange(0, 0));
diff --git a/apps/meteor/client/views/admin/permissions/EditRolePage.tsx b/apps/meteor/client/views/admin/permissions/EditRolePage.tsx
index 8a73018431fb6..281a394df6517 100644
--- a/apps/meteor/client/views/admin/permissions/EditRolePage.tsx
+++ b/apps/meteor/client/views/admin/permissions/EditRolePage.tsx
@@ -1,6 +1,6 @@
import type { IRole } from '@rocket.chat/core-typings';
import { Box, ButtonGroup, Button, Margins } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal, ContextualbarFooter, ContextualbarScrollableContent } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useRoute, useEndpoint } from '@rocket.chat/ui-contexts';
import { FormProvider, useForm } from 'react-hook-form';
@@ -37,7 +37,7 @@ const EditRolePage = ({ role, isEnterprise }: { role?: IRole; isEnterprise: bool
},
});
- const handleManageUsers = useEffectEvent(() => {
+ const handleManageUsers = useStableCallback(() => {
if (role?._id) {
usersInRoleRouter.push({
context: 'users-in-role',
@@ -46,7 +46,7 @@ const EditRolePage = ({ role, isEnterprise }: { role?: IRole; isEnterprise: bool
}
});
- const handleSave = useEffectEvent(async (data: EditRolePageFormData) => {
+ const handleSave = useStableCallback(async (data: EditRolePageFormData) => {
try {
if (data.roleId) {
await updateRole(data);
@@ -62,7 +62,7 @@ const EditRolePage = ({ role, isEnterprise }: { role?: IRole; isEnterprise: bool
}
});
- const handleDelete = useEffectEvent(async () => {
+ const handleDelete = useStableCallback(async () => {
if (!role?._id) {
return;
}
diff --git a/apps/meteor/client/views/admin/permissions/PermissionsContextBar.tsx b/apps/meteor/client/views/admin/permissions/PermissionsContextBar.tsx
index af1176e7ef23d..800db56b1e7a1 100644
--- a/apps/meteor/client/views/admin/permissions/PermissionsContextBar.tsx
+++ b/apps/meteor/client/views/admin/permissions/PermissionsContextBar.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarHeader, ContextualbarTitle, ContextualbarClose, ContextualbarDialog } from '@rocket.chat/ui-client';
import { useRouteParameter, useRoute, useTranslation, useSetModal } from '@rocket.chat/ui-contexts';
import { useEffect } from 'react';
@@ -15,7 +15,7 @@ const PermissionsContextBar = () => {
const setModal = useSetModal();
const { isPending, data: hasCustomRolesModule = false } = useHasLicenseModule('custom-roles');
- const handleCloseContextualbar = useEffectEvent(() => {
+ const handleCloseContextualbar = useStableCallback(() => {
router.push({});
});
diff --git a/apps/meteor/client/views/admin/permissions/PermissionsPage.tsx b/apps/meteor/client/views/admin/permissions/PermissionsPage.tsx
index 8c4061dc00d75..dbe69ffb5cea1 100644
--- a/apps/meteor/client/views/admin/permissions/PermissionsPage.tsx
+++ b/apps/meteor/client/views/admin/permissions/PermissionsPage.tsx
@@ -1,5 +1,5 @@
import { Margins, Tabs, Button } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { usePagination, Page, PageHeader, PageContent } from '@rocket.chat/ui-client';
import { useRoute, usePermission, useSetModal } from '@rocket.chat/ui-contexts';
import { useState } from 'react';
@@ -23,21 +23,21 @@ const PermissionsPage = ({ isEnterprise }: { isEnterprise: boolean }) => {
const paginationData = usePagination();
const { permissions, total, roleList } = usePermissionsAndRoles(type, filter, paginationData.itemsPerPage, paginationData.current);
- const handlePermissionsTab = useEffectEvent(() => {
+ const handlePermissionsTab = useStableCallback(() => {
if (type === 'permissions') {
return;
}
setType('permissions');
});
- const handleSettingsTab = useEffectEvent(() => {
+ const handleSettingsTab = useStableCallback(() => {
if (type === 'settings') {
return;
}
setType('settings');
});
- const handleAdd = useEffectEvent(() => {
+ const handleAdd = useStableCallback(() => {
if (!isEnterprise) {
setModal( setModal(null)} />);
return;
diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTableFilter.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTableFilter.tsx
index 0137b18a47c95..fe371671a816b 100644
--- a/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTableFilter.tsx
+++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/PermissionsTableFilter.tsx
@@ -1,6 +1,6 @@
import { TextInput } from '@rocket.chat/fuselage';
-import { useEffectEvent, useDebouncedValue } from '@rocket.chat/fuselage-hooks';
-import type { FormEvent } from 'react';
+import { useStableCallback, useDebouncedValue } from '@rocket.chat/fuselage-hooks';
+import type { ChangeEvent } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
@@ -13,7 +13,7 @@ const PermissionsTableFilter = ({ onChange }: { onChange: (debouncedFilter: stri
onChange(debouncedFilter);
}, [debouncedFilter, onChange]);
- const handleFilter = useEffectEvent(({ currentTarget: { value } }: FormEvent) => {
+ const handleFilter = useStableCallback(({ currentTarget: { value } }: ChangeEvent) => {
setFilter(value);
});
diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleCell.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleCell.tsx
index 87714561fb523..54b71f6e8e47e 100644
--- a/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleCell.tsx
+++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleCell.tsx
@@ -1,6 +1,6 @@
import type { IRole } from '@rocket.chat/core-typings';
import { Margins, Box, CheckBox, Throbber } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal, GenericTableCell } from '@rocket.chat/ui-client';
import { useSetModal } from '@rocket.chat/ui-contexts';
import { useState, memo } from 'react';
@@ -27,7 +27,7 @@ const RoleCell = ({ _id, name, description, onChange, permissionId, permissionNa
const isRestrictedForRole = AuthorizationUtils.isPermissionRestrictedForRole(permissionId, _id);
const shouldDisplayConfirmation = confirmationRequiredPermissions.includes(permissionId) && grantedRoles.length === 1 && granted;
- const handleChange = useEffectEvent(() => {
+ const handleChange = useStableCallback(() => {
if (shouldDisplayConfirmation) {
const handleSubmit = () => {
handleConfirmChange();
@@ -44,7 +44,7 @@ const RoleCell = ({ _id, name, description, onChange, permissionId, permissionNa
return handleConfirmChange();
});
- const handleConfirmChange = useEffectEvent(async () => {
+ const handleConfirmChange = useStableCallback(async () => {
setLoading(true);
const result = await onChange(_id, granted);
setGranted(result);
diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleHeader.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleHeader.tsx
index 257a6b43a5cc4..c7447ad589347 100644
--- a/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleHeader.tsx
+++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleHeader.tsx
@@ -1,6 +1,6 @@
import type { IRole } from '@rocket.chat/core-typings';
import { Button } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericTableHeaderCell } from '@rocket.chat/ui-client';
import { useRoute } from '@rocket.chat/ui-contexts';
import { memo } from 'react';
@@ -14,7 +14,7 @@ type RoleHeaderProps = {
const RoleHeader = ({ _id, name, description }: RoleHeaderProps) => {
const router = useRoute('admin-permissions');
- const handleEditRole = useEffectEvent(() => {
+ const handleEditRole = useStableCallback(() => {
router.push({
context: 'edit',
_id,
diff --git a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRolePage.tsx b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRolePage.tsx
index 26ec249dcdd06..17c7801b9941d 100644
--- a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRolePage.tsx
+++ b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRolePage.tsx
@@ -1,6 +1,6 @@
import type { IRole, IRoom } from '@rocket.chat/core-typings';
import { Box, Field, FieldLabel, FieldRow, Margins, ButtonGroup, Button, Callout, FieldError } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { usePagination, Page, PageHeader, PageContent } from '@rocket.chat/ui-client';
import { useToastMessageDispatch, useEndpoint, useRouter } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
@@ -38,7 +38,7 @@ const UsersInRolePage = ({ role }: { role: IRole }) => {
const roomFieldId = useId();
const usersFieldId = useId();
- const handleAdd = useEffectEvent(async ({ users, rid }: UsersInRolePayload) => {
+ const handleAdd = useStableCallback(async ({ users, rid }: UsersInRolePayload) => {
try {
await Promise.all(
users.map(async (user) => {
diff --git a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTableRow.tsx b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTableRow.tsx
index a306d58f8f3be..7c10c9ec72382 100644
--- a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTableRow.tsx
+++ b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTableRow.tsx
@@ -1,6 +1,6 @@
import type { IUserInRole, Serialized } from '@rocket.chat/core-typings';
import { Box, IconButton } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { UserAvatar } from '@rocket.chat/ui-avatar';
import { GenericTableRow, GenericTableCell } from '@rocket.chat/ui-client';
import { memo } from 'react';
@@ -18,7 +18,7 @@ const UsersInRoleTableRow = ({ user, onRemove }: UsersInRoleTableRowProps) => {
const { _id, name, username, avatarETag } = user;
const email = getUserEmailAddress(user);
- const handleRemove = useEffectEvent(() => {
+ const handleRemove = useStableCallback(() => {
onRemove(username);
});
diff --git a/apps/meteor/client/views/admin/permissions/UsersInRole/hooks/useRemoveUserFromRole.tsx b/apps/meteor/client/views/admin/permissions/UsersInRole/hooks/useRemoveUserFromRole.tsx
index 14d188058246a..debb28f5e9e77 100644
--- a/apps/meteor/client/views/admin/permissions/UsersInRole/hooks/useRemoveUserFromRole.tsx
+++ b/apps/meteor/client/views/admin/permissions/UsersInRole/hooks/useRemoveUserFromRole.tsx
@@ -1,5 +1,5 @@
import type { IRole, IRoom, IUserInRole } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -24,7 +24,7 @@ export const useRemoveUserFromRole = ({
const removeUserFromRoleEndpoint = useEndpoint('POST', '/v1/roles.removeUserFromRole');
- const handleRemove = useEffectEvent((username: IUserInRole['username']) => {
+ const handleRemove = useStableCallback((username: IUserInRole['username']) => {
const remove = async () => {
try {
if (!username) throw new Error('Username is required');
diff --git a/apps/meteor/client/views/admin/permissions/hooks/useChangeRole.ts b/apps/meteor/client/views/admin/permissions/hooks/useChangeRole.ts
index 0537131c46d96..a42f811533955 100644
--- a/apps/meteor/client/views/admin/permissions/hooks/useChangeRole.ts
+++ b/apps/meteor/client/views/admin/permissions/hooks/useChangeRole.ts
@@ -1,5 +1,5 @@
import type { IRole, IPermission } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
export const useChangeRole = ({
@@ -13,7 +13,7 @@ export const useChangeRole = ({
}): ((roleId: IRole['_id'], granted: boolean) => Promise) => {
const dispatchToastMessage = useToastMessageDispatch();
- return useEffectEvent(async (roleId: IRole['_id'], granted: boolean) => {
+ return useStableCallback(async (roleId: IRole['_id'], granted: boolean) => {
try {
if (granted) {
await onRemove(permissionId, roleId);
diff --git a/apps/meteor/client/views/admin/rooms/EditRoom.tsx b/apps/meteor/client/views/admin/rooms/EditRoom.tsx
index 7b5de5c711c92..0123a800fdefe 100644
--- a/apps/meteor/client/views/admin/rooms/EditRoom.tsx
+++ b/apps/meteor/client/views/admin/rooms/EditRoom.tsx
@@ -13,7 +13,7 @@ import {
TextAreaInput,
FieldError,
} from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarScrollableContent, ContextualbarFooter } from '@rocket.chat/ui-client';
import { useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useId } from 'react';
@@ -97,7 +97,7 @@ const EditRoom = ({ room, onChange, onDelete, onClose }: EditRoomProps) => {
const handleArchive = useArchiveRoom(room);
- const handleUpdateRoomData = useEffectEvent(async ({ isDefault, favorite, ...formData }: EditRoomFormData) => {
+ const handleUpdateRoomData = useStableCallback(async ({ isDefault, favorite, ...formData }: EditRoomFormData) => {
const data = getDirtyFields(formData, dirtyFields);
delete data.archived;
@@ -117,7 +117,7 @@ const EditRoom = ({ room, onChange, onDelete, onClose }: EditRoomProps) => {
}
});
- const handleSave = useEffectEvent((data: EditRoomFormData) =>
+ const handleSave = useStableCallback((data: EditRoomFormData) =>
Promise.all([isDirty && handleUpdateRoomData(data), changeArchiving && handleArchive()].filter(Boolean)),
);
diff --git a/apps/meteor/client/views/admin/rooms/RoomsPage.tsx b/apps/meteor/client/views/admin/rooms/RoomsPage.tsx
index 4ebc9aaa917e0..051dc42dc909c 100644
--- a/apps/meteor/client/views/admin/rooms/RoomsPage.tsx
+++ b/apps/meteor/client/views/admin/rooms/RoomsPage.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarDialog, Page, PageHeader, PageContent } from '@rocket.chat/ui-client';
import { useRouteParameter, useRouter } from '@rocket.chat/ui-contexts';
import { useRef } from 'react';
@@ -14,7 +14,7 @@ const RoomsPage = () => {
const context = useRouteParameter('context');
const reloadRef = useRef(() => null);
- const handleCloseContextualbar = useEffectEvent(() => router.navigate('/admin/rooms'));
+ const handleCloseContextualbar = useStableCallback(() => router.navigate('/admin/rooms'));
return (
diff --git a/apps/meteor/client/views/admin/settings/Setting/inputs/BooleanSettingInput.tsx b/apps/meteor/client/views/admin/settings/Setting/inputs/BooleanSettingInput.tsx
index acb4abb5c0db3..71c4207367d83 100644
--- a/apps/meteor/client/views/admin/settings/Setting/inputs/BooleanSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/Setting/inputs/BooleanSettingInput.tsx
@@ -1,5 +1,5 @@
import { Box, Field, FieldHint, FieldLabel, FieldRow, ToggleSwitch } from '@rocket.chat/fuselage';
-import type { SyntheticEvent } from 'react';
+import type { ChangeEvent } from 'react';
import ResetSettingButton from '../ResetSettingButton';
import type { SettingInputProps } from './types';
@@ -18,7 +18,7 @@ function BooleanSettingInput({
onChangeValue,
onResetButtonClick,
}: BooleanSettingInputProps) {
- const handleChange = (event: SyntheticEvent): void => {
+ const handleChange = (event: ChangeEvent): void => {
const value = event.currentTarget.checked;
onChangeValue?.(value);
};
diff --git a/apps/meteor/client/views/admin/settings/Setting/inputs/CodeMirror/CodeMirror.tsx b/apps/meteor/client/views/admin/settings/Setting/inputs/CodeMirror/CodeMirror.tsx
index a58cd35c3c8db..183cc885c26ab 100644
--- a/apps/meteor/client/views/admin/settings/Setting/inputs/CodeMirror/CodeMirror.tsx
+++ b/apps/meteor/client/views/admin/settings/Setting/inputs/CodeMirror/CodeMirror.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { Editor, EditorFromTextArea } from 'codemirror';
import { useCallback, useEffect, useRef, useState } from 'react';
@@ -43,7 +43,7 @@ function CodeMirror({
...props
}: CodeMirrorProps) {
const [value, setValue] = useState(valueProp || defaultValue);
- const handleChange = useEffectEvent(onChange);
+ const handleChange = useStableCallback(onChange);
const editorRef = useRef(null);
const textAreaRef = useCallback(
diff --git a/apps/meteor/client/views/admin/settings/Setting/inputs/FontSettingInput.tsx b/apps/meteor/client/views/admin/settings/Setting/inputs/FontSettingInput.tsx
index 815bc657b71e6..ba65bda228428 100644
--- a/apps/meteor/client/views/admin/settings/Setting/inputs/FontSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/Setting/inputs/FontSettingInput.tsx
@@ -1,5 +1,5 @@
import { Field, FieldHint, FieldLabel, FieldRow, TextInput } from '@rocket.chat/fuselage';
-import type { FormEventHandler } from 'react';
+import type { ChangeEventHandler } from 'react';
import ResetSettingButton from '../ResetSettingButton';
import type { SettingInputProps } from './types';
@@ -22,7 +22,7 @@ function FontSettingInput({
onChangeValue,
onResetButtonClick,
}: FontSettingInputProps) {
- const handleChange: FormEventHandler = (event): void => {
+ const handleChange: ChangeEventHandler = (event): void => {
onChangeValue?.(event.currentTarget.value);
};
diff --git a/apps/meteor/client/views/admin/settings/Setting/inputs/GenericSettingInput.tsx b/apps/meteor/client/views/admin/settings/Setting/inputs/GenericSettingInput.tsx
index 0e5e901255d73..e2e93c8f01a54 100644
--- a/apps/meteor/client/views/admin/settings/Setting/inputs/GenericSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/Setting/inputs/GenericSettingInput.tsx
@@ -1,5 +1,5 @@
import { Field, FieldHint, FieldLabel, FieldRow, TextInput } from '@rocket.chat/fuselage';
-import type { FormEventHandler } from 'react';
+import type { ChangeEventHandler } from 'react';
import ResetSettingButton from '../ResetSettingButton';
import type { SettingInputProps } from './types';
@@ -22,7 +22,7 @@ function GenericSettingInput({
onChangeValue,
onResetButtonClick,
}: GenericSettingInputProps) {
- const handleChange: FormEventHandler = (event): void => {
+ const handleChange: ChangeEventHandler = (event): void => {
onChangeValue?.(event.currentTarget.value);
};
diff --git a/apps/meteor/client/views/admin/settings/Setting/inputs/IntSettingInput.tsx b/apps/meteor/client/views/admin/settings/Setting/inputs/IntSettingInput.tsx
index 7b3747ef3f474..8fda81726d045 100644
--- a/apps/meteor/client/views/admin/settings/Setting/inputs/IntSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/Setting/inputs/IntSettingInput.tsx
@@ -1,5 +1,5 @@
import { Field, FieldHint, FieldLabel, FieldRow, InputBox } from '@rocket.chat/fuselage';
-import type { FormEventHandler } from 'react';
+import type { ChangeEventHandler } from 'react';
import ResetSettingButton from '../ResetSettingButton';
import type { SettingInputProps } from './types';
@@ -22,7 +22,7 @@ function IntSettingInput({
hasResetButton,
onResetButtonClick,
}: IntSettingInputProps) {
- const handleChange: FormEventHandler = (event) => {
+ const handleChange: ChangeEventHandler = (event) => {
onChangeValue?.(parseInt(event.currentTarget.value, 10));
};
diff --git a/apps/meteor/client/views/admin/settings/Setting/inputs/PasswordSettingInput.tsx b/apps/meteor/client/views/admin/settings/Setting/inputs/PasswordSettingInput.tsx
index c3e98e3fd551e..9bc582bc83855 100644
--- a/apps/meteor/client/views/admin/settings/Setting/inputs/PasswordSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/Setting/inputs/PasswordSettingInput.tsx
@@ -1,5 +1,5 @@
import { Field, FieldHint, FieldLabel, FieldRow, PasswordInput } from '@rocket.chat/fuselage';
-import type { EventHandler, SyntheticEvent } from 'react';
+import type { ChangeEventHandler } from 'react';
import ResetSettingButton from '../ResetSettingButton';
import type { SettingInputProps } from './types';
@@ -20,7 +20,7 @@ function PasswordSettingInput({
onChangeValue,
onResetButtonClick,
}: PasswordSettingInputProps) {
- const handleChange: EventHandler> = (event) => {
+ const handleChange: ChangeEventHandler = (event) => {
onChangeValue?.(event.currentTarget.value);
};
diff --git a/apps/meteor/client/views/admin/settings/Setting/inputs/RelativeUrlSettingInput.tsx b/apps/meteor/client/views/admin/settings/Setting/inputs/RelativeUrlSettingInput.tsx
index bf2eb629b94f3..78e42d2ed0239 100644
--- a/apps/meteor/client/views/admin/settings/Setting/inputs/RelativeUrlSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/Setting/inputs/RelativeUrlSettingInput.tsx
@@ -1,6 +1,6 @@
import { Field, FieldHint, FieldLabel, FieldRow, UrlInput } from '@rocket.chat/fuselage';
import { useAbsoluteUrl } from '@rocket.chat/ui-contexts';
-import type { EventHandler, SyntheticEvent } from 'react';
+import type { ChangeEventHandler } from 'react';
import ResetSettingButton from '../ResetSettingButton';
import type { SettingInputProps } from './types';
@@ -23,7 +23,7 @@ function RelativeUrlSettingInput({
}: RelativeUrlSettingInputProps) {
const getAbsoluteUrl = useAbsoluteUrl();
- const handleChange: EventHandler> = (event) => {
+ const handleChange: ChangeEventHandler = (event) => {
onChangeValue?.(event.currentTarget.value);
};
diff --git a/apps/meteor/client/views/admin/settings/Setting/inputs/StringSettingInput.tsx b/apps/meteor/client/views/admin/settings/Setting/inputs/StringSettingInput.tsx
index 8db88c13f514e..2c096cbc4dee7 100644
--- a/apps/meteor/client/views/admin/settings/Setting/inputs/StringSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/Setting/inputs/StringSettingInput.tsx
@@ -1,5 +1,5 @@
import { Field, FieldHint, FieldLabel, FieldRow, TextAreaInput, TextInput } from '@rocket.chat/fuselage';
-import type { EventHandler, SyntheticEvent } from 'react';
+import type { ChangeEventHandler } from 'react';
import ResetSettingButton from '../ResetSettingButton';
import type { SettingInputProps } from './types';
@@ -27,7 +27,7 @@ function StringSettingInput({
onChangeValue,
onResetButtonClick,
}: StringSettingInputProps) {
- const handleChange: EventHandler> = (event) => {
+ const handleChange: ChangeEventHandler = (event) => {
onChangeValue?.(event.currentTarget.value);
};
diff --git a/apps/meteor/client/views/admin/settings/Setting/inputs/TimespanSettingInput.tsx b/apps/meteor/client/views/admin/settings/Setting/inputs/TimespanSettingInput.tsx
index 8ec08a18f22a6..7a65556a4b7b7 100644
--- a/apps/meteor/client/views/admin/settings/Setting/inputs/TimespanSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/Setting/inputs/TimespanSettingInput.tsx
@@ -1,5 +1,5 @@
import { Field, FieldHint, FieldLabel, FieldRow, InputBox, Select } from '@rocket.chat/fuselage';
-import type { FormEventHandler, Key } from 'react';
+import type { ChangeEventHandler, Key } from 'react';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -55,7 +55,7 @@ function TimespanSettingInput({
const [timeUnit, setTimeUnit] = useState(getHighestTimeUnit(Number(value)));
const [internalValue, setInternalValue] = useState(msToTimeUnit(timeUnit, Number(value)));
- const handleChange: FormEventHandler = (event) => {
+ const handleChange: ChangeEventHandler = (event) => {
const newValue = sanitizeInputValue(Number(event.currentTarget.value));
onChangeValue?.(timeUnitToMs(timeUnit, newValue));
diff --git a/apps/meteor/client/views/admin/settings/SettingsGroupPage/SettingsGroupPage.tsx b/apps/meteor/client/views/admin/settings/SettingsGroupPage/SettingsGroupPage.tsx
index 10947f5758f28..f4b5da5304e44 100644
--- a/apps/meteor/client/views/admin/settings/SettingsGroupPage/SettingsGroupPage.tsx
+++ b/apps/meteor/client/views/admin/settings/SettingsGroupPage/SettingsGroupPage.tsx
@@ -1,10 +1,10 @@
import type { ISetting, ISettingColor } from '@rocket.chat/core-typings';
import { Accordion, Box, Button, ButtonGroup } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { Page, PageHeader, PageScrollableContentWithShadow, PageFooter } from '@rocket.chat/ui-client';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useToastMessageDispatch, useSettingsDispatch, useSettings } from '@rocket.chat/ui-contexts';
-import type { ReactNode, FormEvent, MouseEvent } from 'react';
+import type { ReactNode, MouseEvent, FormEvent } from 'react';
import { useMemo, memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -57,7 +57,7 @@ const SettingsGroupPage = ({
const isColorSetting = (setting: ISetting): setting is ISettingColor => setting.type === 'color';
- const save = useEffectEvent(async () => {
+ const save = useStableCallback(async () => {
const changes = changedEditableSettings.map((setting) => {
if (isColorSetting(setting)) {
return {
@@ -87,7 +87,7 @@ const SettingsGroupPage = ({
const dispatchToEditing = useEditableSettingsDispatch();
- const cancel = useEffectEvent(() => {
+ const cancel = useStableCallback(() => {
const settingsToDispatch = changedEditableSettings
.map(({ _id }) => originalSettings.find((setting) => setting._id === _id))
.map((setting) => {
diff --git a/apps/meteor/client/views/admin/settings/SettingsSection/SettingsSection.tsx b/apps/meteor/client/views/admin/settings/SettingsSection/SettingsSection.tsx
index b2aca79c78234..2ace5e246d1ec 100644
--- a/apps/meteor/client/views/admin/settings/SettingsSection/SettingsSection.tsx
+++ b/apps/meteor/client/views/admin/settings/SettingsSection/SettingsSection.tsx
@@ -1,6 +1,6 @@
import { isSetting, isSettingColor } from '@rocket.chat/core-typings';
import { AccordionItem, Box, Button, FieldGroup } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import type { ReactNode } from 'react';
import { useMemo } from 'react';
@@ -42,7 +42,7 @@ function SettingsSection({ groupId, hasReset = true, sectionName, currentTab, so
const dispatch = useEditableSettingsDispatch();
- const reset = useEffectEvent(() => {
+ const reset = useStableCallback(() => {
dispatch(
editableSettings
.filter(({ disabled }) => !disabled)
diff --git a/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx b/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx
index e862afaf03ecb..2e323d4962354 100644
--- a/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx
+++ b/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx
@@ -1,9 +1,9 @@
import type { ISetting } from '@rocket.chat/core-typings';
import { Button, Box, TextInput, Field, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useSetting, useEndpoint } from '@rocket.chat/ui-contexts';
-import type { FormEvent } from 'react';
+import type { ChangeEvent } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -24,7 +24,7 @@ function LDAPGroupPage({ _id, i18nLabel, onClickBack, ...group }: LDAPGroupPageP
const testSearch = useEndpoint('POST', '/v1/ldap.testSearch');
const ldapEnabled = useSetting('LDAP_Enable');
const setModal = useSetModal();
- const closeModal = useEffectEvent(() => setModal());
+ const closeModal = useStableCallback(() => setModal());
const handleSyncNow = useLdapSync();
const handleLinkClick = useExternalLink();
@@ -53,7 +53,7 @@ function LDAPGroupPage({ _id, i18nLabel, onClickBack, ...group }: LDAPGroupPageP
try {
await testConnection();
let username = '';
- const handleChangeUsername = (event: FormEvent): void => {
+ const handleChangeUsername = (event: ChangeEvent): void => {
username = event.currentTarget.value;
};
@@ -71,7 +71,7 @@ function LDAPGroupPage({ _id, i18nLabel, onClickBack, ...group }: LDAPGroupPageP
wrapperFunction={(props) => (
{
+ onSubmit={(e) => {
e.preventDefault();
confirmSearch();
}}
diff --git a/apps/meteor/client/views/admin/users/AdminUserForm.tsx b/apps/meteor/client/views/admin/users/AdminUserForm.tsx
index a20d7d0afd294..2816f2d1b0400 100644
--- a/apps/meteor/client/views/admin/users/AdminUserForm.tsx
+++ b/apps/meteor/client/views/admin/users/AdminUserForm.tsx
@@ -17,7 +17,7 @@ import {
Skeleton,
} from '@rocket.chat/fuselage';
import type { SelectOption } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { UserCreateParamsPOST } from '@rocket.chat/rest-typings';
import { validateEmail } from '@rocket.chat/tools';
import { CustomFieldsForm, ContextualbarScrollableContent, ContextualbarFooter } from '@rocket.chat/ui-client';
@@ -170,7 +170,7 @@ const AdminUserForm = ({ userData, onReload, context, refetchUserFormData, roleD
},
});
- const handleSaveUser = useEffectEvent(async (userFormPayload: UserFormProps) => {
+ const handleSaveUser = useStableCallback(async (userFormPayload: UserFormProps) => {
const { avatar, passwordConfirmation, ...userFormData } = userFormPayload;
if (!isNewUserPage && userData?._id) {
diff --git a/apps/meteor/client/views/admin/users/AdminUserFormWithData.tsx b/apps/meteor/client/views/admin/users/AdminUserFormWithData.tsx
index 5ac84d9c55e92..342d2752a6972 100644
--- a/apps/meteor/client/views/admin/users/AdminUserFormWithData.tsx
+++ b/apps/meteor/client/views/admin/users/AdminUserFormWithData.tsx
@@ -1,6 +1,6 @@
import type { IRole, IUser, Serialized } from '@rocket.chat/core-typings';
import { Box, Callout } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from 'react-i18next';
import AdminUserForm from './AdminUserForm';
@@ -19,7 +19,7 @@ const AdminUserFormWithData = ({ uid, onReload, context, roleData, roleError }:
const { t } = useTranslation();
const { data, isPending, isError, refetch } = useUserInfoQuery({ userId: uid });
- const handleReload = useEffectEvent(() => {
+ const handleReload = useStableCallback(() => {
onReload();
refetch();
});
diff --git a/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx b/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx
index 118025907bdf1..bd4aafe002f4d 100644
--- a/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx
+++ b/apps/meteor/client/views/admin/users/AdminUserInfoWithData.tsx
@@ -1,6 +1,6 @@
import type { IUser } from '@rocket.chat/core-typings';
import { Callout } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarContent } from '@rocket.chat/ui-client';
import { useSetting, useRolesDescription, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
@@ -41,7 +41,7 @@ const AdminUserInfoWithData = ({ uid, onReload, tab }: AdminUserInfoWithDataProp
},
});
- const onChange = useEffectEvent(() => {
+ const onChange = useStableCallback(() => {
onReload();
refetch();
});
diff --git a/apps/meteor/client/views/admin/users/AdminUsersPage.tsx b/apps/meteor/client/views/admin/users/AdminUsersPage.tsx
index a8e5a0325f15e..2fe17c50a6e60 100644
--- a/apps/meteor/client/views/admin/users/AdminUsersPage.tsx
+++ b/apps/meteor/client/views/admin/users/AdminUsersPage.tsx
@@ -1,6 +1,6 @@
import type { LicenseInfo } from '@rocket.chat/core-typings';
import { Callout, ContextualbarIcon, Skeleton, Tabs, TabsItem } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { OptionProp } from '@rocket.chat/ui-client';
import {
ExternalLink,
@@ -96,7 +96,7 @@ const AdminUsersPage = () => {
sortData.setSort(tab === 'pending' ? 'active' : 'name', 'asc');
};
- const handleCloseContextualbar = useEffectEvent(() => router.navigate('/admin/users'));
+ const handleCloseContextualbar = useStableCallback(() => router.navigate('/admin/users'));
useEffect(() => {
prevSearchTerm.current = searchTerm;
diff --git a/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx b/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx
index 8f752e0385af1..38983b5f0df2c 100644
--- a/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx
+++ b/apps/meteor/client/views/admin/users/UsersTable/UsersTable.tsx
@@ -1,6 +1,6 @@
import type { IRole, IUser, Serialized } from '@rocket.chat/core-typings';
import { Pagination } from '@rocket.chat/fuselage';
-import { useEffectEvent, useBreakpoints } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useBreakpoints } from '@rocket.chat/fuselage-hooks';
import type { DefaultUserInfo } from '@rocket.chat/rest-typings';
import {
GenericTable,
@@ -65,7 +65,7 @@ const UsersTable = ({
return (event as KeyboardEvent).key !== undefined;
};
- const handleClickOrKeyDown = useEffectEvent((id: IUser['_id'], e: MouseEvent | KeyboardEvent): void => {
+ const handleClickOrKeyDown = useStableCallback((id: IUser['_id'], e: MouseEvent | KeyboardEvent): void => {
e.stopPropagation();
const keyboardSubmitKeys = ['Enter', ' '];
diff --git a/apps/meteor/client/views/admin/users/UsersTable/UsersTableFilters.tsx b/apps/meteor/client/views/admin/users/UsersTable/UsersTableFilters.tsx
index 42e007de6f508..e43bc1a6c73d4 100644
--- a/apps/meteor/client/views/admin/users/UsersTable/UsersTableFilters.tsx
+++ b/apps/meteor/client/views/admin/users/UsersTable/UsersTableFilters.tsx
@@ -3,7 +3,7 @@ import { Box, Icon, Margins, TextInput } from '@rocket.chat/fuselage';
import { useBreakpoints } from '@rocket.chat/fuselage-hooks';
import type { OptionProp } from '@rocket.chat/ui-client';
import { MultiSelectCustom } from '@rocket.chat/ui-client';
-import type { Dispatch, FormEvent, SetStateAction } from 'react';
+import type { ChangeEvent, Dispatch, SetStateAction } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -21,7 +21,7 @@ const UsersTableFilters = ({ roleData, setUsersFilters }: UsersTableFiltersProps
const [text, setText] = useState('');
const handleSearchTextChange = useCallback(
- (event: FormEvent) => {
+ (event: ChangeEvent) => {
setText(event.currentTarget.value);
setUsersFilters({ text: event.currentTarget.value, roles: selectedRoles });
},
@@ -66,7 +66,7 @@ const UsersTableFilters = ({ roleData, setUsersFilters }: UsersTableFiltersProps
) => {
+ onSubmit={(event) => {
event.preventDefault();
}}
display='flex'
diff --git a/apps/meteor/client/views/admin/users/hooks/useDeleteUserAction.tsx b/apps/meteor/client/views/admin/users/hooks/useDeleteUserAction.tsx
index 410bd7efbbf90..0fe17d6fbdbca 100644
--- a/apps/meteor/client/views/admin/users/hooks/useDeleteUserAction.tsx
+++ b/apps/meteor/client/views/admin/users/hooks/useDeleteUserAction.tsx
@@ -1,5 +1,5 @@
import type { IUser } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import {
@@ -56,7 +56,7 @@ export const useDeleteUserAction = (userId: IUser['_id'], onChange: () => void,
onChange,
);
- const confirmDeleteUser = useEffectEvent(() => {
+ const confirmDeleteUser = useStableCallback(() => {
setModal(
setModal()} confirmText={t('Delete')}>
{t(`Delete_User_Warning_${erasureType}` as TranslationKey)}
diff --git a/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx b/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx
index e30cc3e721546..d9398865d7778 100644
--- a/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx
+++ b/apps/meteor/client/views/admin/workspace/DeploymentCard/DeploymentCard.tsx
@@ -1,6 +1,6 @@
import type { IWorkspaceInfo, IStats } from '@rocket.chat/core-typings';
import { Button, Card, CardBody, CardControls, Margins } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { IInstance } from '@rocket.chat/rest-typings';
import { useSetModal } from '@rocket.chat/ui-contexts';
import { memo } from 'react';
@@ -24,7 +24,7 @@ const DeploymentCard = ({ serverInfo: { info, cloudWorkspaceId }, statistics, in
const { commit = {}, marketplaceApiVersion: appsEngineVersion } = info || {};
- const handleInstancesModal = useEffectEvent(() => {
+ const handleInstancesModal = useStableCallback(() => {
setModal( setModal()} />);
});
diff --git a/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx b/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx
index e0095feb37cf4..356f2290e6f49 100644
--- a/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx
+++ b/apps/meteor/client/views/admin/workspace/UsersUploadsCard/UsersUploadsCard.tsx
@@ -1,6 +1,6 @@
import type { IStats } from '@rocket.chat/core-typings';
import { Button, Card, CardBody, CardControls, Margins } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRouter } from '@rocket.chat/ui-contexts';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -21,7 +21,7 @@ const UsersUploadsCard = ({ statistics }: UsersUploadsCardProps) => {
const router = useRouter();
- const handleEngagement = useEffectEvent(() => {
+ const handleEngagement = useStableCallback(() => {
router.navigate('/admin/engagement');
});
diff --git a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionButton.tsx b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionButton.tsx
index 0f9431c6658c2..cd7bb003c8777 100644
--- a/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionButton.tsx
+++ b/apps/meteor/client/views/admin/workspace/VersionCard/components/VersionCardActionButton.tsx
@@ -1,5 +1,5 @@
import { Button } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { LocationPathname } from '@rocket.chat/ui-contexts';
import { useRouter } from '@rocket.chat/ui-contexts';
import type { ReactNode } from 'react';
@@ -18,7 +18,7 @@ type VersionCardActionButtonProps =
const VersionCardActionButton = (item: VersionCardActionButtonProps) => {
const router = useRouter();
- const handleActionButton = useEffectEvent(() => {
+ const handleActionButton = useStableCallback(() => {
if ('action' in item) {
return item.action();
}
diff --git a/apps/meteor/client/views/audit/components/forms/DateRangePicker.tsx b/apps/meteor/client/views/audit/components/forms/DateRangePicker.tsx
index da65923a97234..a593fb3a9b55e 100644
--- a/apps/meteor/client/views/audit/components/forms/DateRangePicker.tsx
+++ b/apps/meteor/client/views/audit/components/forms/DateRangePicker.tsx
@@ -1,8 +1,8 @@
import { Box, InputBox, Margins } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericMenu } from '@rocket.chat/ui-client';
import { startOfDay, endOfDay, startOfWeek, endOfWeek, startOfMonth, endOfMonth, subDays, subWeeks, subMonths, parseISO } from 'date-fns';
-import type { ComponentProps, SetStateAction, FormEvent } from 'react';
+import type { ComponentProps, SetStateAction, ChangeEvent } from 'react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -125,16 +125,16 @@ type DateRangePickerProps = Omit, 'value' | 'onChange
const minDate = (a: Date, b: Date) => (a.getTime() < b.getTime() ? a : b);
const DateRangePicker = ({ value, onChange, ...props }: DateRangePickerProps) => {
- const dispatch = useEffectEvent((action: DateRangeAction): void => {
+ const dispatch = useStableCallback((action: DateRangeAction): void => {
const newRange = dateRangeReducer(value ?? { start: undefined, end: undefined }, action);
onChange?.(newRange);
});
- const handleChangeStart = useEffectEvent(({ currentTarget }: FormEvent) => {
+ const handleChangeStart = useStableCallback(({ currentTarget }: ChangeEvent) => {
dispatch({ newStart: currentTarget.value });
});
- const handleChangeEnd = useEffectEvent(({ currentTarget }: FormEvent) => {
+ const handleChangeEnd = useStableCallback(({ currentTarget }: ChangeEvent) => {
dispatch({ newEnd: currentTarget.value });
});
diff --git a/apps/meteor/client/views/audit/hooks/useAuditTab.ts b/apps/meteor/client/views/audit/hooks/useAuditTab.ts
index 42d24c5af3c00..363302a770412 100644
--- a/apps/meteor/client/views/audit/hooks/useAuditTab.ts
+++ b/apps/meteor/client/views/audit/hooks/useAuditTab.ts
@@ -1,5 +1,5 @@
import type { IAuditLog } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRoute, useRouteParameter } from '@rocket.chat/ui-contexts';
import type { SetStateAction } from 'react';
import { useMemo } from 'react';
@@ -19,7 +19,7 @@ export const useAuditTab = () => {
const auditRoute = useRoute('audit-home');
- const setType = useEffectEvent((newType: SetStateAction) => {
+ const setType = useStableCallback((newType: SetStateAction) => {
auditRoute.replace({ tab: typeToTabMap[typeof newType === 'function' ? newType(type) : newType] ?? 'rooms' });
});
diff --git a/apps/meteor/client/views/banners/UiKitBanner.tsx b/apps/meteor/client/views/banners/UiKitBanner.tsx
index 523efd7a9eda8..22d316e144179 100644
--- a/apps/meteor/client/views/banners/UiKitBanner.tsx
+++ b/apps/meteor/client/views/banners/UiKitBanner.tsx
@@ -1,5 +1,5 @@
import { Banner, Icon } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { UiKitContext, bannerParser, UiKitBanner as UiKitBannerSurfaceRender, UiKitComponent } from '@rocket.chat/fuselage-ui-kit';
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type * as UiKit from '@rocket.chat/ui-kit';
@@ -32,7 +32,7 @@ const UiKitBanner = ({ initialView }: UiKitBannerProps) => {
}, [view.icon]);
const dispatchToastMessage = useToastMessageDispatch();
- const handleClose = useEffectEvent(() => {
+ const handleClose = useStableCallback(() => {
void actionManager
.emitInteraction(view.appId, {
type: 'viewClosed',
diff --git a/apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx b/apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx
index 211f2c2f84200..3cc442eca3e48 100644
--- a/apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx
+++ b/apps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsx
@@ -1,6 +1,6 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { Box, Icon, Throbber } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { MessageComposerAction } from '@rocket.chat/ui-composer';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -23,7 +23,7 @@ const AudioMessageRecorder = ({ rid, isMicrophoneDenied }: AudioMessageRecorderP
const [recordingInterval, setRecordingInterval] = useState | null>(null);
const [recordingRoomId, setRecordingRoomId] = useState(null);
- const stopRecording = useEffectEvent(async () => {
+ const stopRecording = useStableCallback(async () => {
if (recordingInterval) {
clearInterval(recordingInterval);
}
@@ -41,13 +41,13 @@ const AudioMessageRecorder = ({ rid, isMicrophoneDenied }: AudioMessageRecorderP
return blob;
});
- const handleUnmount = useEffectEvent(async () => {
+ const handleUnmount = useStableCallback(async () => {
if (state === 'recording') {
await stopRecording();
}
});
- const handleRecord = useEffectEvent(async () => {
+ const handleRecord = useStableCallback(async () => {
chat?.composer?.setRecordingMode(true);
if (recordingRoomId && recordingRoomId !== rid) {
@@ -74,13 +74,13 @@ const AudioMessageRecorder = ({ rid, isMicrophoneDenied }: AudioMessageRecorderP
}
});
- const handleCancelButtonClick = useEffectEvent(async () => {
+ const handleCancelButtonClick = useStableCallback(async () => {
await stopRecording();
});
const chat = useChat();
- const handleDoneButtonClick = useEffectEvent(async () => {
+ const handleDoneButtonClick = useStableCallback(async () => {
setState('loading');
const blob = await stopRecording();
diff --git a/apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx b/apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx
index 8a7131ab41fab..6a551712c2dc5 100644
--- a/apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx
+++ b/apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx
@@ -1,7 +1,7 @@
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
import { css } from '@rocket.chat/css-in-js';
import { Box, ButtonGroup, Button, Icon, PositionAnimated } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type { AllHTMLAttributes, RefObject } from 'react';
import { useRef, useEffect, useState } from 'react';
@@ -94,7 +94,7 @@ const VideoMessageRecorder = ({ rid, tmid, reference }: VideoMessageRecorderProp
stopVideoRecording(rid, tmid);
};
- const handleCancel = useEffectEvent(() => {
+ const handleCancel = useStableCallback(() => {
VideoRecorder.stop();
chat?.composer?.setRecordingVideo(false);
setTime(undefined);
diff --git a/apps/meteor/client/views/hooks/roomActions/useArchiveRoom.tsx b/apps/meteor/client/views/hooks/roomActions/useArchiveRoom.tsx
index d60e6717fc41d..cdd48f2e7d44e 100644
--- a/apps/meteor/client/views/hooks/roomActions/useArchiveRoom.tsx
+++ b/apps/meteor/client/views/hooks/roomActions/useArchiveRoom.tsx
@@ -1,5 +1,5 @@
import type { IRoom, RoomAdminFieldsType } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -8,7 +8,7 @@ export const useArchiveRoom = (room: Pick) => {
const dispatchToastMessage = useToastMessageDispatch();
const archiveAction = useEndpoint('POST', '/v1/rooms.changeArchivationState');
- const handleArchive = useEffectEvent(async () => {
+ const handleArchive = useStableCallback(async () => {
try {
await archiveAction({ rid: room._id, action: room.archived ? 'unarchive' : 'archive' });
dispatchToastMessage({ type: 'success', message: room.archived ? t('Room_has_been_unarchived') : t('Room_has_been_archived') });
diff --git a/apps/meteor/client/views/hooks/roomActions/useDeleteRoom.tsx b/apps/meteor/client/views/hooks/roomActions/useDeleteRoom.tsx
index 155097874efdc..80c377a583abd 100644
--- a/apps/meteor/client/views/hooks/roomActions/useDeleteRoom.tsx
+++ b/apps/meteor/client/views/hooks/roomActions/useDeleteRoom.tsx
@@ -1,6 +1,6 @@
import type { IRoom, RoomAdminFieldsType } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useRouter, usePermission, useEndpoint } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
@@ -69,7 +69,7 @@ export const useDeleteRoom = (room: IRoom | Pick, {
const isDeleting = deleteTeamMutation.isPending || deleteRoomMutation.isPending;
- const handleDelete = useEffectEvent(() => {
+ const handleDelete = useStableCallback(() => {
const handleDeleteTeam = async (roomsToRemove: IRoom['_id'][]) => {
if (!room.teamId) {
return;
diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.spec.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.spec.tsx
index 59a544526b6b7..a713d8890d5f9 100644
--- a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.spec.tsx
+++ b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.spec.tsx
@@ -1,6 +1,7 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import type { ReactNode } from 'react';
import AppDetailsPage from './AppDetailsPage';
import { AppClientOrchestratorInstance } from '../../../apps/orchestrator';
@@ -25,8 +26,8 @@ jest.mock('@rocket.chat/ui-client', () => {
const originalModule = jest.requireActual('@rocket.chat/ui-client');
return {
...originalModule,
- PageHeader: ({ children }: { children: React.ReactNode }) => {children}
,
- PageFooter: ({ children, isDirty }: { children: React.ReactNode; isDirty: boolean }) => isDirty && {children}
,
+ PageHeader: ({ children }: { children: ReactNode }) => {children}
,
+ PageFooter: ({ children, isDirty }: { children: ReactNode; isDirty: boolean }) => isDirty && {children}
,
};
});
diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx
index f19cba712cec0..6d1a2e808d939 100644
--- a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx
+++ b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx
@@ -1,6 +1,6 @@
import type { App, SettingValue } from '@rocket.chat/core-typings';
import { Button, ButtonGroup, Box } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { Page, PageFooter, PageHeader, PageScrollableContentWithShadow } from '@rocket.chat/ui-client';
import { useTranslation, useRouteParameter, useToastMessageDispatch, usePermission, useRouter } from '@rocket.chat/ui-contexts';
import { useMemo, useCallback } from 'react';
@@ -41,7 +41,7 @@ const AppDetailsPage = ({ id }: AppDetailsPageProps) => {
const appData = useAppInfo(id, context || '');
const compactMode = useCompactMode();
- const handleReturn = useEffectEvent((): void => {
+ const handleReturn = useStableCallback((): void => {
if (!context) {
return;
}
@@ -52,7 +52,7 @@ const AppDetailsPage = ({ id }: AppDetailsPageProps) => {
});
});
- const handleReturnToLogs = useEffectEvent((): void => {
+ const handleReturnToLogs = useStableCallback((): void => {
if (!context) {
return;
}
diff --git a/apps/meteor/client/views/marketplace/components/RadioDropDown/RadioDownAnchor.tsx b/apps/meteor/client/views/marketplace/components/RadioDropDown/RadioDownAnchor.tsx
index a12f40bd16126..940e0dbccf44f 100644
--- a/apps/meteor/client/views/marketplace/components/RadioDropDown/RadioDownAnchor.tsx
+++ b/apps/meteor/client/views/marketplace/components/RadioDropDown/RadioDownAnchor.tsx
@@ -1,12 +1,12 @@
import type { Button } from '@rocket.chat/fuselage';
import { Box, Icon } from '@rocket.chat/fuselage';
-import type { ComponentProps } from 'react';
+import type { ComponentProps, MouseEvent } from 'react';
import { forwardRef } from 'react';
import type { RadioDropDownGroup } from '../../definitions/RadioDropDownDefinitions';
type RadioDropdownAnchorProps = {
- onClick: (event: React.MouseEvent) => void;
+ onClick: (event: MouseEvent) => void;
group: RadioDropDownGroup;
} & Omit, 'onClick'>;
diff --git a/apps/meteor/client/views/mediaCallHistory/CallHistoryPageFilters.tsx b/apps/meteor/client/views/mediaCallHistory/CallHistoryPageFilters.tsx
index 56c4942227169..e1f333c571db0 100644
--- a/apps/meteor/client/views/mediaCallHistory/CallHistoryPageFilters.tsx
+++ b/apps/meteor/client/views/mediaCallHistory/CallHistoryPageFilters.tsx
@@ -2,7 +2,7 @@ import { Box, Icon, TextInput, Select } from '@rocket.chat/fuselage';
import type { OptionProp } from '@rocket.chat/ui-client';
import { MultiSelectCustom } from '@rocket.chat/ui-client';
import { useCallback, useMemo, useState } from 'react';
-import type { FormEvent, Key } from 'react';
+import type { ChangeEvent, Key, FormEvent } from 'react';
import { useTranslation } from 'react-i18next';
type StatesFilter = Array<'ended' | 'transferred' | 'not-answered' | 'failed'>;
@@ -79,7 +79,7 @@ const CallHistoryPageFilters = ({ onChangeText, onChangeType, onChangeStates, se
return (
e.preventDefault(), [])}
+ onSubmit={useCallback((e: FormEvent) => e.preventDefault(), [])}
mb='x8'
display='flex'
flexWrap='wrap'
@@ -92,7 +92,7 @@ const CallHistoryPageFilters = ({ onChangeText, onChangeType, onChangeStates, se
alignItems='center'
placeholder={t('Search_calls')}
addon={}
- onChange={(e: FormEvent) => onChangeText(e.currentTarget.value)}
+ onChange={(e: ChangeEvent) => onChangeText(e.currentTarget.value)}
value={searchText}
/>
diff --git a/apps/meteor/client/views/mediaCallHistory/useMediaCallInternalHistoryActions.ts b/apps/meteor/client/views/mediaCallHistory/useMediaCallInternalHistoryActions.ts
index f3458197217b3..6ea35964a8a4e 100644
--- a/apps/meteor/client/views/mediaCallHistory/useMediaCallInternalHistoryActions.ts
+++ b/apps/meteor/client/views/mediaCallHistory/useMediaCallInternalHistoryActions.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useGoToDirectMessage } from '@rocket.chat/ui-client';
import { useRouter, useUserAvatarPath } from '@rocket.chat/ui-contexts';
import { useWidgetExternalControls, usePeekMediaSessionState, type CallHistoryInternalContact } from '@rocket.chat/ui-voip';
@@ -25,7 +25,7 @@ export const useMediaCallInternalHistoryActions = ({
const getAvatarUrl = useUserAvatarPath();
- const voiceCall = useEffectEvent(() => {
+ const voiceCall = useStableCallback(() => {
if (state !== 'available') {
return;
}
@@ -41,7 +41,7 @@ export const useMediaCallInternalHistoryActions = ({
const goToDirectMessage = useGoToDirectMessage({ username: contact.username }, openRoomId ?? '');
- const jumpToMessage = useEffectEvent(() => {
+ const jumpToMessage = useStableCallback(() => {
const rid = messageRoomId || openRoomId;
if (!messageId || !rid) {
return;
@@ -56,7 +56,7 @@ export const useMediaCallInternalHistoryActions = ({
});
});
- const userInfo = useEffectEvent(() => {
+ const userInfo = useStableCallback(() => {
if (!openUserInfo) {
return;
}
diff --git a/apps/meteor/client/views/modal/uikit/UiKitModal.tsx b/apps/meteor/client/views/modal/uikit/UiKitModal.tsx
index a9159b9ee1cbf..7ffc4c1a3868b 100644
--- a/apps/meteor/client/views/modal/uikit/UiKitModal.tsx
+++ b/apps/meteor/client/views/modal/uikit/UiKitModal.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { UiKitContext } from '@rocket.chat/fuselage-ui-kit';
import { MarkupInteractionContext } from '@rocket.chat/gazzodown';
import type * as UiKit from '@rocket.chat/ui-kit';
@@ -21,7 +21,7 @@ const UiKitModal = ({ initialView }: UiKitModalProps) => {
const { view, errors, values, updateValues, state } = useUiKitView(initialView);
const contextValue = useModalContextValue({ view, errors, values, updateValues });
- const handleSubmit = useEffectEvent((e: FormEvent) => {
+ const handleSubmit = useStableCallback((e: FormEvent) => {
preventSyntheticEvent(e);
void actionManager.emitInteraction(view.appId, {
type: 'viewSubmit',
@@ -35,7 +35,7 @@ const UiKitModal = ({ initialView }: UiKitModalProps) => {
});
});
- const handleCancel = useEffectEvent((e: FormEvent) => {
+ const handleCancel = useStableCallback((e: FormEvent) => {
preventSyntheticEvent(e);
void actionManager.emitInteraction(view.appId, {
type: 'viewClosed',
@@ -50,7 +50,7 @@ const UiKitModal = ({ initialView }: UiKitModalProps) => {
});
});
- const handleClose = useEffectEvent(() => {
+ const handleClose = useStableCallback(() => {
void actionManager.emitInteraction(view.appId, {
type: 'viewClosed',
payload: {
diff --git a/apps/meteor/client/views/navigation/contexts/RoomsNavigationContext.ts b/apps/meteor/client/views/navigation/contexts/RoomsNavigationContext.ts
index 1ece998128a1e..ce6d09bdca41d 100644
--- a/apps/meteor/client/views/navigation/contexts/RoomsNavigationContext.ts
+++ b/apps/meteor/client/views/navigation/contexts/RoomsNavigationContext.ts
@@ -1,5 +1,5 @@
import { type ISubscription, type ILivechatInquiryRecord, type IRoom, isTeamRoom, isDirectMessageRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent, useLocalStorage } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useLocalStorage } from '@rocket.chat/fuselage-hooks';
import type { Keys as IconName } from '@rocket.chat/icons';
import { isTruthy } from '@rocket.chat/tools';
import type { SubscriptionWithRoom, TranslationKey } from '@rocket.chat/ui-contexts';
@@ -230,7 +230,7 @@ export const useUnreadOnlyToggle = (): [boolean, () => void] => {
const { setFilter, parentRid } = useRoomsListContext();
const [currentTab, unread] = useSidePanelFilter();
- return [unread, useEffectEvent(() => setFilter(currentTab, !unread, parentRid))];
+ return [unread, useStableCallback(() => setFilter(currentTab, !unread, parentRid))];
};
export const useSwitchSidePanelTab = () => {
diff --git a/apps/meteor/client/views/navigation/hooks/useSidePanelParentRid.ts b/apps/meteor/client/views/navigation/hooks/useSidePanelParentRid.ts
index e7c2d2d98ab97..73593c2a08671 100644
--- a/apps/meteor/client/views/navigation/hooks/useSidePanelParentRid.ts
+++ b/apps/meteor/client/views/navigation/hooks/useSidePanelParentRid.ts
@@ -1,12 +1,12 @@
import type { IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent, useLocalStorage } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useLocalStorage } from '@rocket.chat/fuselage-hooks';
import { collapsibleFilters, type AllGroupsKeys } from '../contexts/RoomsNavigationContext';
export const useSidePanelParentRid = () => {
const [parentRid, setParentRid] = useLocalStorage('sidePanelParentRid', undefined);
- const setParentRoom = useEffectEvent((filter: AllGroupsKeys, parentRid: IRoom['_id'] | undefined) => {
+ const setParentRoom = useStableCallback((filter: AllGroupsKeys, parentRid: IRoom['_id'] | undefined) => {
if (collapsibleFilters.some((group) => filter === group)) {
setParentRid(parentRid);
}
diff --git a/apps/meteor/client/views/navigation/providers/RoomsNavigationProvider.tsx b/apps/meteor/client/views/navigation/providers/RoomsNavigationProvider.tsx
index 103845e79d07b..23ae31a1d0b85 100644
--- a/apps/meteor/client/views/navigation/providers/RoomsNavigationProvider.tsx
+++ b/apps/meteor/client/views/navigation/providers/RoomsNavigationProvider.tsx
@@ -1,6 +1,6 @@
import { isDirectMessageRoom, isDiscussion, isOmnichannelRoom, isPrivateRoom, isPublicRoom, isTeamRoom } from '@rocket.chat/core-typings';
import type { ILivechatInquiryRecord, IRoom } from '@rocket.chat/core-typings';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { SubscriptionWithRoom, TranslationKey } from '@rocket.chat/ui-contexts';
import { useSetting, useUserPreference, useUserSubscriptions, useLayout } from '@rocket.chat/ui-contexts';
import type { ReactNode } from 'react';
@@ -159,7 +159,7 @@ const RoomsNavigationContextProvider = ({ children }: { children: ReactNode }) =
const [currentFilter, unread, , setCurrentFilter] = useSidePanelFilter();
- const setFilter = useEffectEvent((filter: AllGroupsKeys, unread: boolean, parentRid?: IRoom['_id']) => {
+ const setFilter = useStableCallback((filter: AllGroupsKeys, unread: boolean, parentRid?: IRoom['_id']) => {
openSidePanel();
setCurrentFilter(getFilterKey(filter, unread));
setParentRoom(filter, parentRid);
@@ -167,7 +167,7 @@ const RoomsNavigationContextProvider = ({ children }: { children: ReactNode }) =
const [groups, unreadGroupData] = useRoomsGroups();
- const handleRoomOpened = useEffectEvent((rid: string) => {
+ const handleRoomOpened = useStableCallback((rid: string) => {
const room = Rooms.use.getState().find((r) => r._id === rid);
if (!room) {
diff --git a/apps/meteor/client/views/omnichannel/additionalForms/AutoCompleteUnit.tsx b/apps/meteor/client/views/omnichannel/additionalForms/AutoCompleteUnit.tsx
index a9cb772b92e6f..c8a74e9e7439d 100644
--- a/apps/meteor/client/views/omnichannel/additionalForms/AutoCompleteUnit.tsx
+++ b/apps/meteor/client/views/omnichannel/additionalForms/AutoCompleteUnit.tsx
@@ -1,5 +1,5 @@
import { PaginatedSelectFiltered } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { ComponentProps } from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -34,7 +34,7 @@ const AutoCompleteUnit = ({
const { data: unitsList, fetchNextPage } = useUnitsList({ filter: debouncedUnitFilter, haveNone });
- const handleLoadItems = useEffectEvent(onLoadItems);
+ const handleLoadItems = useStableCallback(onLoadItems);
useEffect(() => {
handleLoadItems(unitsList);
diff --git a/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx b/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx
index d7efcbd7b5ac7..81096bc2ac675 100644
--- a/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx
+++ b/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx
@@ -1,7 +1,7 @@
import type { ILivechatAgent, ILivechatAgentStatus, ILivechatDepartmentAgents } from '@rocket.chat/core-typings';
import { Field, FieldLabel, FieldGroup, FieldRow, TextInput, Button, Box, Icon, Select, ButtonGroup } from '@rocket.chat/fuselage';
import type { SelectOption } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
ContextualbarTitle,
ContextualbarClose,
@@ -79,7 +79,7 @@ const AgentEdit = ({ agentData, agentDepartments }: AgentEditProps) => {
const saveAgentInfo = useEndpoint('POST', '/v1/livechat/agents.saveInfo');
const saveAgentStatus = useEndpoint('POST', '/v1/livechat/agent.status');
- const handleSave = useEffectEvent(async ({ status, departments, ...data }: AgentEditFormData) => {
+ const handleSave = useStableCallback(async ({ status, departments, ...data }: AgentEditFormData) => {
try {
await saveAgentStatus({ agentId: agentData._id, status });
await saveAgentInfo({
diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx
index b6b08ddd52958..cb0461b277c34 100644
--- a/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx
+++ b/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarDialog, Page, PageHeader, PageContent } from '@rocket.chat/ui-client';
import { usePermission, useRouteParameter, useRouter } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -15,7 +15,7 @@ const AgentsPage = () => {
const context = useRouteParameter('context');
const id = useRouteParameter('id');
const router = useRouter();
- const handleCloseContextualbar = useEffectEvent(() => router.navigate('/omnichannel/agents'));
+ const handleCloseContextualbar = useStableCallback(() => router.navigate('/omnichannel/agents'));
if (!canViewAgents) {
return ;
diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsTable/AddAgent.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsTable/AddAgent.tsx
index 5b23d29d22f02..8668518cdd312 100644
--- a/apps/meteor/client/views/omnichannel/agents/AgentsTable/AddAgent.tsx
+++ b/apps/meteor/client/views/omnichannel/agents/AgentsTable/AddAgent.tsx
@@ -1,5 +1,5 @@
import { Button, Box, Field, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { UserAutoComplete } from '@rocket.chat/ui-client';
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -25,7 +25,7 @@ const AddAgent = () => {
},
});
- const handleSave = useEffectEvent(async () => {
+ const handleSave = useStableCallback(async () => {
await saveAction({ username });
});
diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsTable/AgentsTable.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsTable/AgentsTable.tsx
index adfe8269f8347..3cb6a49f3db67 100644
--- a/apps/meteor/client/views/omnichannel/agents/AgentsTable/AgentsTable.tsx
+++ b/apps/meteor/client/views/omnichannel/agents/AgentsTable/AgentsTable.tsx
@@ -1,5 +1,5 @@
import { Pagination } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useMediaQuery, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useMediaQuery, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableBody,
@@ -40,7 +40,7 @@ const AgentsTable = () => {
const [defaultQuery] = useState(hashKey([query]));
const queryHasChanged = defaultQuery !== hashKey([query]);
- const onHeaderClick = useEffectEvent((id: 'name' | 'username' | 'emails.address' | 'statusLivechat') => {
+ const onHeaderClick = useStableCallback((id: 'name' | 'username' | 'emails.address' | 'statusLivechat') => {
if (sortBy === id) {
setSort(id, sortDirection === 'asc' ? 'desc' : 'asc');
return;
diff --git a/apps/meteor/client/views/omnichannel/agents/hooks/useRemoveAgent.tsx b/apps/meteor/client/views/omnichannel/agents/hooks/useRemoveAgent.tsx
index 670d2eb802312..f8cd635144fdd 100644
--- a/apps/meteor/client/views/omnichannel/agents/hooks/useRemoveAgent.tsx
+++ b/apps/meteor/client/views/omnichannel/agents/hooks/useRemoveAgent.tsx
@@ -1,5 +1,5 @@
import type { ILivechatAgent } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useTranslation, useRouter, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -15,7 +15,7 @@ export const useRemoveAgent = (uid: ILivechatAgent['_id']) => {
const deleteAction = useEndpoint('DELETE', '/v1/livechat/users/agent/:_id', { _id: uid });
- const handleDelete = useEffectEvent(() => {
+ const handleDelete = useStableCallback(() => {
const onDeleteAgent = async () => {
try {
await deleteAction();
diff --git a/apps/meteor/client/views/omnichannel/analytics/DateRangePicker.tsx b/apps/meteor/client/views/omnichannel/analytics/DateRangePicker.tsx
index 746225ee2f44a..f895ba994f44c 100644
--- a/apps/meteor/client/views/omnichannel/analytics/DateRangePicker.tsx
+++ b/apps/meteor/client/views/omnichannel/analytics/DateRangePicker.tsx
@@ -1,8 +1,8 @@
import { Box, InputBox, Field, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericMenu } from '@rocket.chat/ui-client';
import { subDays, subMonths, startOfMonth, endOfMonth, format } from 'date-fns';
-import type { ComponentProps, FormEvent } from 'react';
+import type { ComponentProps, ChangeEvent } from 'react';
import { useState, useMemo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
@@ -39,7 +39,7 @@ const DateRangePicker = ({ onChange = () => undefined, ...props }: DateRangePick
const { start, end } = range;
- const handleStart = useEffectEvent(({ currentTarget }: FormEvent) => {
+ const handleStart = useStableCallback(({ currentTarget }: ChangeEvent) => {
const rangeObj = {
start: currentTarget.value,
end: range.end,
@@ -48,7 +48,7 @@ const DateRangePicker = ({ onChange = () => undefined, ...props }: DateRangePick
onChange(rangeObj);
});
- const handleEnd = useEffectEvent(({ currentTarget }: FormEvent) => {
+ const handleEnd = useStableCallback(({ currentTarget }: ChangeEvent) => {
const rangeObj = {
end: currentTarget.value,
start: range.start,
@@ -57,7 +57,7 @@ const DateRangePicker = ({ onChange = () => undefined, ...props }: DateRangePick
onChange(rangeObj);
});
- const handleRange = useEffectEvent((range: { start: string; end: string }) => {
+ const handleRange = useStableCallback((range: { start: string; end: string }) => {
setRange(range);
onChange(range);
});
diff --git a/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx b/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx
index 87a8c2c4cd953..dba81fc48f925 100644
--- a/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx
+++ b/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts';
import type * as chartjs from 'chart.js';
import { useEffect, useRef } from 'react';
@@ -57,7 +57,7 @@ const InterchangeableChart = ({
const loadData = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts-data');
- const draw = useEffectEvent(
+ const draw = useStableCallback(
async (params: {
daterange: {
from: string;
diff --git a/apps/meteor/client/views/omnichannel/appearance/AppearancePage.tsx b/apps/meteor/client/views/omnichannel/appearance/AppearancePage.tsx
index 390ab0391b2a3..b12b9d1ee03be 100644
--- a/apps/meteor/client/views/omnichannel/appearance/AppearancePage.tsx
+++ b/apps/meteor/client/views/omnichannel/appearance/AppearancePage.tsx
@@ -1,6 +1,6 @@
import type { ISetting, Serialized } from '@rocket.chat/core-typings';
import { ButtonGroup, Button, Box } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { Page, PageHeader, PageScrollableContentWithShadow, PageFooter } from '@rocket.chat/ui-client';
import { useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts';
import { useId } from 'react';
@@ -56,7 +56,7 @@ const AppearancePage = ({ settings }: { settings: Serialized[] }) => {
const currentData = watch();
- const handleSave = useEffectEvent(async (data: LivechatAppearanceSettings) => {
+ const handleSave = useStableCallback(async (data: LivechatAppearanceSettings) => {
const mappedAppearance = Object.entries(data)
.map(([_id, value]) => ({ _id, value }))
.filter((item) => item.value !== undefined) as {
diff --git a/apps/meteor/client/views/omnichannel/businessHours/EditBusinessHours.tsx b/apps/meteor/client/views/omnichannel/businessHours/EditBusinessHours.tsx
index 05288e002224b..47204e0849c08 100644
--- a/apps/meteor/client/views/omnichannel/businessHours/EditBusinessHours.tsx
+++ b/apps/meteor/client/views/omnichannel/businessHours/EditBusinessHours.tsx
@@ -1,6 +1,6 @@
import type { ILivechatBusinessHour, LivechatBusinessHourTypes, Serialized } from '@rocket.chat/core-typings';
import { Box, Button, ButtonGroup } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { canonicalizeTimezone } from '@rocket.chat/tools';
import { Page, PageFooter, PageHeader, PageScrollableContentWithShadow } from '@rocket.chat/ui-client';
import { useToastMessageDispatch, useTranslation, useRouter, useEndpoint } from '@rocket.chat/ui-contexts';
@@ -52,7 +52,7 @@ const EditBusinessHours = ({ businessHourData, type }: EditBusinessHoursProps) =
formState: { isDirty },
} = methods;
- const handleSave = useEffectEvent(async ({ departments, ...data }: BusinessHoursFormData) => {
+ const handleSave = useStableCallback(async ({ departments, ...data }: BusinessHoursFormData) => {
const departmentsToApplyBusinessHour = departments?.map((dep) => dep.value).join(',') || '';
try {
diff --git a/apps/meteor/client/views/omnichannel/businessHours/useRemoveBusinessHour.tsx b/apps/meteor/client/views/omnichannel/businessHours/useRemoveBusinessHour.tsx
index b53598b9006d8..659168834341e 100644
--- a/apps/meteor/client/views/omnichannel/businessHours/useRemoveBusinessHour.tsx
+++ b/apps/meteor/client/views/omnichannel/businessHours/useRemoveBusinessHour.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -11,7 +11,7 @@ export const useRemoveBusinessHour = () => {
const removeBusinessHour = useEndpoint('POST', '/v1/livechat/business-hours.remove');
const queryClient = useQueryClient();
- const handleRemove = useEffectEvent((_id: string, type: string) => {
+ const handleRemove = useStableCallback((_id: string, type: string) => {
const onDeleteBusinessHour = async () => {
try {
await removeBusinessHour({ _id, type });
diff --git a/apps/meteor/client/views/omnichannel/cannedResponses/contextualBar/CannedResponse/CannedResponseList.tsx b/apps/meteor/client/views/omnichannel/cannedResponses/contextualBar/CannedResponse/CannedResponseList.tsx
index 516a70ee0143d..041952b2e5584 100644
--- a/apps/meteor/client/views/omnichannel/cannedResponses/contextualBar/CannedResponse/CannedResponseList.tsx
+++ b/apps/meteor/client/views/omnichannel/cannedResponses/contextualBar/CannedResponse/CannedResponseList.tsx
@@ -11,7 +11,7 @@ import {
ContextualbarDialog,
} from '@rocket.chat/ui-client';
import { useRoomToolbox } from '@rocket.chat/ui-contexts';
-import type { Dispatch, FormEventHandler, MouseEvent, SetStateAction } from 'react';
+import type { Dispatch, ChangeEventHandler, MouseEvent, SetStateAction } from 'react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Virtuoso } from 'react-virtuoso';
@@ -27,7 +27,7 @@ type CannedResponseListProps = {
onClose: () => void;
options: [string, string][];
text: string;
- setText: FormEventHandler;
+ setText: ChangeEventHandler;
type: string;
setType: Dispatch>;
isRoomOverMacLimit: boolean;
diff --git a/apps/meteor/client/views/omnichannel/cannedResponses/contextualBar/CannedResponse/WrapCannedResponseList.tsx b/apps/meteor/client/views/omnichannel/cannedResponses/contextualBar/CannedResponse/WrapCannedResponseList.tsx
index 9109ddcdd1f5c..8f93a34b66af3 100644
--- a/apps/meteor/client/views/omnichannel/cannedResponses/contextualBar/CannedResponse/WrapCannedResponseList.tsx
+++ b/apps/meteor/client/views/omnichannel/cannedResponses/contextualBar/CannedResponse/WrapCannedResponseList.tsx
@@ -1,5 +1,5 @@
import type { IOmnichannelCannedResponse, ILivechatDepartment } from '@rocket.chat/core-typings';
-import { useDebouncedValue, useLocalStorage, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useLocalStorage, useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useRouter, useRoomToolbox } from '@rocket.chat/ui-contexts';
import type { ChangeEvent, MouseEvent } from 'react';
import { memo, useCallback, useState } from 'react';
@@ -34,7 +34,7 @@ export const WrapCannedResponseList = () => {
// TODO: handle pending and error states
const { data, fetchNextPage, refetch } = useCannedResponseList({ filter: debouncedText, type });
- const onClickItem = useEffectEvent(
+ const onClickItem = useStableCallback(
(
data: IOmnichannelCannedResponse & {
departmentName: ILivechatDepartment['name'];
diff --git a/apps/meteor/client/views/omnichannel/cannedResponses/modals/CannedResponsesTable.tsx b/apps/meteor/client/views/omnichannel/cannedResponses/modals/CannedResponsesTable.tsx
index 40b982c809c9b..5e91bf94fa2db 100644
--- a/apps/meteor/client/views/omnichannel/cannedResponses/modals/CannedResponsesTable.tsx
+++ b/apps/meteor/client/views/omnichannel/cannedResponses/modals/CannedResponsesTable.tsx
@@ -1,5 +1,5 @@
import { Box, Pagination } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import { UserAvatar } from '@rocket.chat/ui-avatar';
import {
GenericTable,
@@ -63,9 +63,9 @@ const CannedResponsesTable = () => {
refetchOnWindowFocus: false,
});
- const handleAddNew = useEffectEvent(() => router.navigate('/omnichannel/canned-responses/new'));
+ const handleAddNew = useStableCallback(() => router.navigate('/omnichannel/canned-responses/new'));
- const onRowClick = useEffectEvent((id: string, scope: string) => (): void => {
+ const onRowClick = useStableCallback((id: string, scope: string) => (): void => {
if (scope === 'global' && isMonitor && !isManager) {
return dispatchToastMessage({
type: 'error',
diff --git a/apps/meteor/client/views/omnichannel/cannedResponses/modals/useRemoveCannedResponse.tsx b/apps/meteor/client/views/omnichannel/cannedResponses/modals/useRemoveCannedResponse.tsx
index be706985158e3..fcf69cffcaf95 100644
--- a/apps/meteor/client/views/omnichannel/cannedResponses/modals/useRemoveCannedResponse.tsx
+++ b/apps/meteor/client/views/omnichannel/cannedResponses/modals/useRemoveCannedResponse.tsx
@@ -1,5 +1,5 @@
import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useRouter, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -14,7 +14,7 @@ export const useRemoveCannedResponse = (id: IOmnichannelCannedResponse['_id']) =
const dispatchToastMessage = useToastMessageDispatch();
const removeCannedResponse = useEndpoint('DELETE', '/v1/canned-responses/:_id', { _id: id });
- const handleDelete = useEffectEvent(() => {
+ const handleDelete = useStableCallback(() => {
const onDeleteCannedResponse: () => Promise = async () => {
try {
await removeCannedResponse();
diff --git a/apps/meteor/client/views/omnichannel/components/Tags.tsx b/apps/meteor/client/views/omnichannel/components/Tags.tsx
index 61d2c049740ed..a5ec667458420 100644
--- a/apps/meteor/client/views/omnichannel/components/Tags.tsx
+++ b/apps/meteor/client/views/omnichannel/components/Tags.tsx
@@ -1,5 +1,5 @@
import { TextInput, Chip, Button, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type { ChangeEvent } from 'react';
import { useId, useMemo, useState } from 'react';
@@ -42,7 +42,7 @@ const Tags = ({ tags = [], handler, error, tagRequired, department }: TagsProps)
handler(tags.filter((tag) => tag !== tagToRemove));
};
- const handleTagTextSubmit = useEffectEvent(() => {
+ const handleTagTextSubmit = useStableCallback(() => {
if (!tags) {
return;
}
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.tsx
index cbb0a76a5577b..fe54512dd695d 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.tsx
@@ -1,5 +1,5 @@
import { Box } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
import { Wizard, useWizard, WizardContent, WizardTabs } from '@rocket.chat/ui-client';
import { usePermission } from '@rocket.chat/ui-contexts';
@@ -86,7 +86,7 @@ const OutboundMessageWizard = ({ defaultValues = {}, onSuccess, onError }: Outbo
}
}, [hasOmnichannelModule.data, hasOutboundModule.data, hasProviders, isLoadingModule, isLoadingProviders, upsellModal]);
- const handleSubmit = useEffectEvent((values: SubmitPayload) => {
+ const handleSubmit = useStableCallback((values: SubmitPayload) => {
if (!hasOutboundModule.data) {
upsellModal.open();
return;
@@ -95,7 +95,7 @@ const OutboundMessageWizard = ({ defaultValues = {}, onSuccess, onError }: Outbo
setState((state) => ({ ...state, ...values }));
});
- const handleSend = useEffectEvent(async () => {
+ const handleSend = useStableCallback(async () => {
try {
if (!isRecipientStepValid(state)) {
throw new Error('error-invalid-recipient-step');
@@ -144,7 +144,7 @@ const OutboundMessageWizard = ({ defaultValues = {}, onSuccess, onError }: Outbo
}
});
- const handleDirtyStep = useEffectEvent(() => {
+ const handleDirtyStep = useStableCallback(() => {
wizardApi.resetNextSteps();
});
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx
index e57e44fe01b4b..d171ca0ae6455 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/MessageForm/MessageForm.tsx
@@ -1,6 +1,6 @@
import type { IOutboundProviderTemplate, Serialized, ILivechatContact } from '@rocket.chat/core-typings';
import { Box, Button, FieldGroup, Scrollable } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
import type { ReactNode } from 'react';
import { useId, useMemo } from 'react';
@@ -61,7 +61,7 @@ const MessageForm = (props: MessageFormProps) => {
const parametersMetadata = useMemo(() => (template ? extractParameterMetadata(template) : []), [template]);
const customActions = useMemo(() => renderActions?.({ isSubmitting }), [isSubmitting, renderActions]);
- const submit = useEffectEvent(async (values: MessageFormData) => {
+ const submit = useStableCallback(async (values: MessageFormData) => {
try {
const { templateId, templateParameters } = values;
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplateField.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplateField.tsx
index 1f116ac24c326..259b129008a0e 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplateField.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/MessageForm/components/TemplateField.tsx
@@ -1,6 +1,6 @@
import type { IOutboundProviderTemplate, Serialized } from '@rocket.chat/core-typings';
import { Field, FieldError, FieldHint, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { ComponentProps } from 'react';
import { useId } from 'react';
import type { Control } from 'react-hook-form';
@@ -41,7 +41,7 @@ const TemplateField = ({ control, templates, onChange: onChangeExternal, ...prop
},
});
- const handleTemplateChange = useEffectEvent((value: string) => {
+ const handleTemplateChange = useStableCallback((value: string) => {
onChangeExternal?.(value);
templateField.onChange(value);
});
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.tsx
index 0ecfed32d42d1..8ac31baff588b 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RecipientForm/RecipientForm.tsx
@@ -1,6 +1,6 @@
import type { IOutboundProviderMetadata, Serialized, ILivechatContact } from '@rocket.chat/core-typings';
import { Box, Button, FieldGroup, Scrollable } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
@@ -111,12 +111,12 @@ const RecipientForm = (props: RecipientFormProps) => {
const isContactNotFound = isSuccessContact && !contact;
const isProviderNotFound = isSuccessProvider && !provider;
- const validateContactField = useEffectEvent((shouldValidate = false) => {
+ const validateContactField = useStableCallback((shouldValidate = false) => {
trigger('contactId');
setValue('recipient', '', { shouldValidate });
});
- const validateProviderField = useEffectEvent((shouldValidate = false) => {
+ const validateProviderField = useStableCallback((shouldValidate = false) => {
trigger('providerId');
setValue('sender', '', { shouldValidate });
});
@@ -151,7 +151,7 @@ const RecipientForm = (props: RecipientFormProps) => {
isDirty && onDirty && onDirty();
}, [isDirty, onDirty]);
- const submit = useEffectEvent(async (values: RecipientFormData) => {
+ const submit = useStableCallback(async (values: RecipientFormData) => {
try {
// Wait if contact or provider is still being fetched in background
const [updatedContact, updatedProvider] = await Promise.all([
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/ContactField.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/ContactField.tsx
index 7b18cd452dfc0..fab99b29fa37b 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/ContactField.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RecipientForm/components/ContactField.tsx
@@ -1,5 +1,5 @@
import { Box, Field, FieldError, FieldLabel, FieldRow, Option, OptionContent, OptionDescription } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { UserAvatar } from '@rocket.chat/ui-avatar';
import { useId } from 'react';
import type { ComponentProps } from 'react';
@@ -38,7 +38,7 @@ const ContactField = ({ control, isError = false, isFetching = false, onRetry, .
},
});
- const renderContactOption = useEffectEvent(({ label, ...props }, { phones }) => {
+ const renderContactOption = useStableCallback(({ label, ...props }, { phones }) => {
const phoneList = phones?.map((p) => formatPhoneNumber(p.phoneNumber)).join(', ');
return (
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx
index a0c5e4a17b89b..275f4bf42505d 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RepliesForm/RepliesForm.tsx
@@ -1,6 +1,6 @@
import type { Serialized, ILivechatDepartment, ILivechatDepartmentAgents } from '@rocket.chat/core-typings';
import { Box, Button, FieldGroup, Scrollable } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
import { useEndpoint, usePermission, useUser } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
@@ -88,7 +88,7 @@ const RepliesForm = (props: RepliesFormProps) => {
return () => clearErrors('departmentId');
}, [clearErrors, isErrorDepartment, trigger]);
- const submit = useEffectEvent(async ({ agentId, departmentId }: RepliesFormData) => {
+ const submit = useStableCallback(async ({ agentId, departmentId }: RepliesFormData) => {
try {
const agent = agents?.find((agent) => agent.agentId === agentId);
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/DepartmentField.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/DepartmentField.tsx
index 71842a449892f..324c53078f226 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/DepartmentField.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/forms/RepliesForm/components/DepartmentField.tsx
@@ -1,5 +1,5 @@
import { Field, FieldError, FieldHint, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { ComponentProps } from 'react';
import { useId } from 'react';
import type { Control } from 'react-hook-form';
@@ -43,7 +43,7 @@ const DepartmentField = ({
},
});
- const handleDepartmentChange = useEffectEvent((onChange: (value: string) => void) => {
+ const handleDepartmentChange = useStableCallback((onChange: (value: string) => void) => {
return (value: string) => {
onChangeExternal();
onChange(value);
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/MessageStep.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/MessageStep.tsx
index 6830f6e7b4b83..1cb7382787249 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/MessageStep.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/MessageStep.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useWizardContext, WizardActions, WizardBackButton, WizardNextButton } from '@rocket.chat/ui-client';
import type { ComponentProps } from 'react';
@@ -12,7 +12,7 @@ type MessageStepProps = Omit, 'onSubmit'> & {
const MessageStep = ({ contact, templates, defaultValues, onSubmit }: MessageStepProps) => {
const { next } = useWizardContext();
- const handleSubmit = useEffectEvent(async (values: MessageFormSubmitPayload) => {
+ const handleSubmit = useStableCallback(async (values: MessageFormSubmitPayload) => {
onSubmit(values);
next();
});
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/RecipientStep.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/RecipientStep.tsx
index 88e9c64e7c573..09fc9e73b1cbf 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/RecipientStep.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/RecipientStep.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useWizardContext, WizardActions, WizardNextButton } from '@rocket.chat/ui-client';
import type { RecipientFormData, RecipientFormSubmitPayload } from '../forms/RecipientForm';
@@ -13,7 +13,7 @@ type RecipientStepProps = {
const RecipientStep = ({ defaultValues, onDirty, onSubmit }: RecipientStepProps) => {
const { next } = useWizardContext();
- const handleSubmit = useEffectEvent((values: RecipientFormSubmitPayload) => {
+ const handleSubmit = useStableCallback((values: RecipientFormSubmitPayload) => {
onSubmit(values);
next();
});
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/RepliesStep.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/RepliesStep.tsx
index d3abec35e7aab..aa5b2fe4266a3 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/RepliesStep.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/RepliesStep.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useWizardContext, WizardActions, WizardBackButton, WizardNextButton } from '@rocket.chat/ui-client';
import type { RepliesFormData, RepliesFormSubmitPayload } from '../forms/RepliesForm';
@@ -12,7 +12,7 @@ type RepliesStepProps = {
const RepliesStep = ({ defaultValues, onSubmit }: RepliesStepProps) => {
const { next } = useWizardContext();
- const handleSubmit = useEffectEvent(async (values: RepliesFormSubmitPayload) => {
+ const handleSubmit = useStableCallback(async (values: RepliesFormSubmitPayload) => {
onSubmit(values);
next();
});
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderInput.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderInput.tsx
index 24aa391420f42..9c3144ca08794 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderInput.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/components/TemplatePlaceholderSelector/TemplatePlaceholderInput.tsx
@@ -1,6 +1,6 @@
import type { ILivechatContact, Serialized } from '@rocket.chat/core-typings';
import { Box, Icon, TextInput } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRef, type ComponentProps, type FormEvent, type FormEventHandler } from 'react';
import PlaceholderSelector from './TemplatePlaceholderSelector';
@@ -22,7 +22,7 @@ const TemplatePlaceholderInput = ({ contact, value = '', type, onChange, ...prop
const addon = type === 'media' ? : undefined;
- const handleOpenToggle = useEffectEvent((isOpen: boolean) => {
+ const handleOpenToggle = useStableCallback((isOpen: boolean) => {
if (!isOpen) inputRef.current?.focus();
});
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageModal/OutboundMessageModal.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageModal/OutboundMessageModal.tsx
index 8c81a0dbb4a9e..010781587d0d5 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageModal/OutboundMessageModal.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageModal/OutboundMessageModal.tsx
@@ -1,5 +1,5 @@
import { Modal, ModalBackdrop, ModalClose, ModalContent, ModalHeader, ModalTitle } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRouter } from '@rocket.chat/ui-contexts';
import { useEffect, useId, useState } from 'react';
import type { KeyboardEvent, ComponentProps } from 'react';
@@ -32,7 +32,7 @@ const OutboundMessageModal = ({ defaultValues, onClose }: OutboundMessageModalPr
});
}, [initialRoute, onClose, router]);
- const handleKeyDown = useEffectEvent((e: KeyboardEvent): void => {
+ const handleKeyDown = useStableCallback((e: KeyboardEvent): void => {
if (e.key !== 'Escape') {
return;
}
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageModal/useOutboundMessageModal.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageModal/useOutboundMessageModal.tsx
index b9f8a28f819e8..d508bb7ae4b4a 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageModal/useOutboundMessageModal.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageModal/useOutboundMessageModal.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import { useMemo } from 'react';
@@ -8,9 +8,9 @@ import OutboundMessageModal from './OutboundMessageModal';
export const useOutboundMessageModal = () => {
const setModal = useSetModal();
- const close = useEffectEvent((): void => setModal(null));
+ const close = useStableCallback((): void => setModal(null));
- const open = useEffectEvent((defaultValues?: ComponentProps['defaultValues']) => {
+ const open = useStableCallback((defaultValues?: ComponentProps['defaultValues']) => {
setModal();
});
diff --git a/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx b/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx
index 6234176c44f53..2099789a7ebd4 100644
--- a/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx
+++ b/apps/meteor/client/views/omnichannel/components/outboundMessage/modals/OutboundMessageUpsellModal/useOutboundMessageUpsellModal.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useLicense } from '@rocket.chat/ui-client';
import { useRole, useSetModal } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
@@ -12,8 +12,8 @@ export const useOutboundMessageUpsellModal = () => {
const license = useLicense();
const { data: hasModule = false } = useHasLicenseModule('outbound-messaging');
- const close = useEffectEvent(() => setModal(null));
- const open = useEffectEvent(() =>
+ const close = useStableCallback(() => setModal(null));
+ const open = useStableCallback(() =>
setModal(),
);
diff --git a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsPage.tsx b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsPage.tsx
index 1fcf092ede7c3..582502566f419 100644
--- a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsPage.tsx
+++ b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsPage.tsx
@@ -1,5 +1,5 @@
import { Button } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarDialog, Page, PageHeader, PageContent } from '@rocket.chat/ui-client';
import { useRouteParameter, useRouter } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -15,7 +15,7 @@ const CustomFieldsPage = () => {
const context = useRouteParameter('context');
const id = useRouteParameter('id');
- const handleCloseContextualbar = useEffectEvent(() => router.navigate('/omnichannel/customfields'));
+ const handleCloseContextualbar = useStableCallback(() => router.navigate('/omnichannel/customfields'));
return (
diff --git a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx
index d249d816e4e70..e041566a0ad07 100644
--- a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx
+++ b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx
@@ -1,5 +1,5 @@
import { IconButton, Pagination } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableHeader,
@@ -29,8 +29,8 @@ const CustomFieldsTable = () => {
const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination();
const { sortBy, sortDirection, setSort } = useSort<'_id' | 'label' | 'scope' | 'visibility'>('_id');
- const handleAddNew = useEffectEvent(() => router.navigate('/omnichannel/customfields/new'));
- const onRowClick = useEffectEvent((id: string) => () => router.navigate(`/omnichannel/customfields/edit/${id}`));
+ const handleAddNew = useStableCallback(() => router.navigate('/omnichannel/customfields/new'));
+ const onRowClick = useStableCallback((id: string) => () => router.navigate(`/omnichannel/customfields/edit/${id}`));
const handleDelete = useRemoveCustomField();
diff --git a/apps/meteor/client/views/omnichannel/customFields/useRemoveCustomField.tsx b/apps/meteor/client/views/omnichannel/customFields/useRemoveCustomField.tsx
index d03059d91e8ae..358b0a76ea1c0 100644
--- a/apps/meteor/client/views/omnichannel/customFields/useRemoveCustomField.tsx
+++ b/apps/meteor/client/views/omnichannel/customFields/useRemoveCustomField.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -13,7 +13,7 @@ export const useRemoveCustomField = () => {
const removeCustomField = useEndpoint('POST', '/v1/livechat/custom-fields.delete');
const queryClient = useQueryClient();
- const handleDelete = useEffectEvent((id: string) => {
+ const handleDelete = useStableCallback((id: string) => {
const onDeleteAgent = async () => {
try {
await removeCustomField({ customFieldId: id });
diff --git a/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/AddAgent.tsx b/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/AddAgent.tsx
index 54c9ca78758d3..fa42c958f9c15 100644
--- a/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/AddAgent.tsx
+++ b/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/AddAgent.tsx
@@ -1,5 +1,5 @@
import { Box, Button } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type { AriaAttributes } from 'react';
import { useState } from 'react';
@@ -23,9 +23,9 @@ function AddAgent({ agentList, onAdd, 'aria-labelledby': ariaLabelledBy }: AddAg
const dispatchToastMessage = useToastMessageDispatch();
- const handleAgent = useEffectEvent((e: string) => setUserId(e));
+ const handleAgent = useStableCallback((e: string) => setUserId(e));
- const handleSave = useEffectEvent(async () => {
+ const handleSave = useStableCallback(async () => {
if (!userId) {
return;
}
diff --git a/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/RemoveAgentButton.tsx b/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/RemoveAgentButton.tsx
index b7af68c27e25c..96de0b04b7072 100644
--- a/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/RemoveAgentButton.tsx
+++ b/apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/RemoveAgentButton.tsx
@@ -1,5 +1,5 @@
import { IconButton } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type { MouseEvent } from 'react';
@@ -10,7 +10,7 @@ function RemoveAgentButton({ agentId, onRemove }: { agentId: string; onRemove: (
const dispatchToastMessage = useToastMessageDispatch();
const { t } = useTranslation();
- const handleDelete = useEffectEvent((e: MouseEvent) => {
+ const handleDelete = useStableCallback((e: MouseEvent) => {
e.stopPropagation();
const onRemoveAgent = async () => {
diff --git a/apps/meteor/client/views/omnichannel/departments/DepartmentTags.tsx b/apps/meteor/client/views/omnichannel/departments/DepartmentTags.tsx
index 36815bde66c91..f8854c6575791 100644
--- a/apps/meteor/client/views/omnichannel/departments/DepartmentTags.tsx
+++ b/apps/meteor/client/views/omnichannel/departments/DepartmentTags.tsx
@@ -1,5 +1,5 @@
import { Button, Chip, FieldRow, TextInput } from '@rocket.chat/fuselage';
-import type { ComponentProps, FormEvent } from 'react';
+import type { ComponentProps, ChangeEvent } from 'react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -33,7 +33,7 @@ const DepartmentTags = ({ error, value: tags, onChange, ...props }: DepartmentTa
error={error}
placeholder={t('Enter_a_tag')}
value={tagText}
- onChange={(e: FormEvent) => setTagText(e.currentTarget.value)}
+ onChange={(e: ChangeEvent) => setTagText(e.currentTarget.value)}
{...props}
/>
diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts
index 73402836654e1..d4c86b55a80dc 100644
--- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts
+++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type * as chartjs from 'chart.js';
import type { TFunction } from 'i18next';
import type { MutableRefObject } from 'react';
@@ -18,7 +18,7 @@ export function useUpdateChartData({
init,
t,
}: UseUpdateChartDataOptions>) {
- return useEffectEvent(async (label: string, data: number[]) => {
+ return useStableCallback(async (label: string, data: number[]) => {
const canvas = canvasRef.current;
if (!canvas) {
diff --git a/apps/meteor/client/views/omnichannel/reports/components/AgentsTable.tsx b/apps/meteor/client/views/omnichannel/reports/components/AgentsTable.tsx
index 1b89a8777b100..e65e7364806e3 100644
--- a/apps/meteor/client/views/omnichannel/reports/components/AgentsTable.tsx
+++ b/apps/meteor/client/views/omnichannel/reports/components/AgentsTable.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableBody,
@@ -23,7 +23,7 @@ type AgentsTableProps = {
export const AgentsTable = memo(({ data, sortBy, sortDirection, setSort }: AgentsTableProps) => {
const { t } = useTranslation();
- const onHeaderClick = useEffectEvent((id: 'name' | 'total') => {
+ const onHeaderClick = useStableCallback((id: 'name' | 'total') => {
setSort(id, sortDirection === 'asc' ? 'desc' : 'asc');
});
diff --git a/apps/meteor/client/views/omnichannel/slaPolicies/RemoveSlaButton.tsx b/apps/meteor/client/views/omnichannel/slaPolicies/RemoveSlaButton.tsx
index ae74f28585b63..9a3537050dbc3 100644
--- a/apps/meteor/client/views/omnichannel/slaPolicies/RemoveSlaButton.tsx
+++ b/apps/meteor/client/views/omnichannel/slaPolicies/RemoveSlaButton.tsx
@@ -1,5 +1,5 @@
import { IconButton } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal, GenericTableCell } from '@rocket.chat/ui-client';
import { useRoute, useEndpoint, useSetModal, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import type { MouseEvent } from 'react';
@@ -13,7 +13,7 @@ const RemoveSlaButton = ({ _id, reload }: { _id: string; reload: () => void }) =
const removeSLA = useEndpoint('DELETE', `/v1/livechat/sla/:slaId`, { slaId: _id });
- const handleDelete = useEffectEvent((e: MouseEvent) => {
+ const handleDelete = useStableCallback((e: MouseEvent) => {
e.stopPropagation();
const onDeleteAgent = async (): Promise => {
try {
diff --git a/apps/meteor/client/views/omnichannel/slaPolicies/SlaPage.tsx b/apps/meteor/client/views/omnichannel/slaPolicies/SlaPage.tsx
index 5ddfe97c49a96..17bf86f99fe07 100644
--- a/apps/meteor/client/views/omnichannel/slaPolicies/SlaPage.tsx
+++ b/apps/meteor/client/views/omnichannel/slaPolicies/SlaPage.tsx
@@ -1,5 +1,5 @@
import { Button, ButtonGroup } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
ContextualbarTitle,
ContextualbarHeader,
@@ -29,7 +29,7 @@ const SlaPage = () => {
reload.current();
}, []);
- const handleClick = useEffectEvent(() =>
+ const handleClick = useStableCallback(() =>
slaPoliciesRoute.push({
context: 'new',
}),
diff --git a/apps/meteor/client/views/omnichannel/slaPolicies/SlaTable.tsx b/apps/meteor/client/views/omnichannel/slaPolicies/SlaTable.tsx
index 43fd80f41ec02..38ec897dfeecc 100644
--- a/apps/meteor/client/views/omnichannel/slaPolicies/SlaTable.tsx
+++ b/apps/meteor/client/views/omnichannel/slaPolicies/SlaTable.tsx
@@ -1,5 +1,5 @@
import { Pagination } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableHeaderCell,
@@ -56,8 +56,8 @@ const SlaTable = ({ reload }: { reload: MutableRefObject<() => void> }) => {
reload.current = refetch;
}, [reload, refetch]);
- const handleAddNew = useEffectEvent(() => router.navigate('/omnichannel/sla-policies/new'));
- const onRowClick = useEffectEvent((id: string) => () => router.navigate(`/omnichannel/sla-policies/edit/${id}`));
+ const handleAddNew = useStableCallback(() => router.navigate('/omnichannel/sla-policies/new'));
+ const onRowClick = useStableCallback((id: string) => () => router.navigate(`/omnichannel/sla-policies/edit/${id}`));
const headers = (
<>
diff --git a/apps/meteor/client/views/omnichannel/tags/TagsPage.tsx b/apps/meteor/client/views/omnichannel/tags/TagsPage.tsx
index 41e365fc26c58..e831c6de35f08 100644
--- a/apps/meteor/client/views/omnichannel/tags/TagsPage.tsx
+++ b/apps/meteor/client/views/omnichannel/tags/TagsPage.tsx
@@ -1,5 +1,5 @@
import { Button, ButtonGroup } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarDialog, Page, PageHeader, PageContent } from '@rocket.chat/ui-client';
import { useRouter, useTranslation, useRouteParameter } from '@rocket.chat/ui-contexts';
@@ -13,7 +13,7 @@ const TagsPage = () => {
const context = useRouteParameter('context');
const id = useRouteParameter('id');
- const handleCloseContextualbar = useEffectEvent(() => router.navigate('/omnichannel/tags'));
+ const handleCloseContextualbar = useStableCallback(() => router.navigate('/omnichannel/tags'));
return (
diff --git a/apps/meteor/client/views/omnichannel/tags/TagsTable.tsx b/apps/meteor/client/views/omnichannel/tags/TagsTable.tsx
index 2515e82a1fa29..7ae09a4d07b01 100644
--- a/apps/meteor/client/views/omnichannel/tags/TagsTable.tsx
+++ b/apps/meteor/client/views/omnichannel/tags/TagsTable.tsx
@@ -1,5 +1,5 @@
import { IconButton, Pagination } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableRow,
@@ -28,8 +28,8 @@ const TagsTable = () => {
const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination();
const { sortBy, sortDirection, setSort } = useSort<'name' | 'description'>('name');
- const onRowClick = useEffectEvent((id: string) => router.navigate(`/omnichannel/tags/edit/${id}`));
- const handleAddNew = useEffectEvent(() => router.navigate('/omnichannel/tags/new'));
+ const onRowClick = useStableCallback((id: string) => router.navigate(`/omnichannel/tags/edit/${id}`));
+ const handleAddNew = useStableCallback(() => router.navigate('/omnichannel/tags/new'));
const handleDeleteTag = useRemoveTag();
const query = useDebouncedValue(
diff --git a/apps/meteor/client/views/omnichannel/tags/useRemoveTag.tsx b/apps/meteor/client/views/omnichannel/tags/useRemoveTag.tsx
index 9864e0801861c..77c31654e820b 100644
--- a/apps/meteor/client/views/omnichannel/tags/useRemoveTag.tsx
+++ b/apps/meteor/client/views/omnichannel/tags/useRemoveTag.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useRouter, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -12,7 +12,7 @@ export const useRemoveTag = () => {
const queryClient = useQueryClient();
const router = useRouter();
- const handleDeleteTag = useEffectEvent((tagId: string) => {
+ const handleDeleteTag = useStableCallback((tagId: string) => {
const handleDelete = async () => {
try {
await removeTag({ id: tagId });
diff --git a/apps/meteor/client/views/omnichannel/triggers/TriggersPage.tsx b/apps/meteor/client/views/omnichannel/triggers/TriggersPage.tsx
index 50eeb1dddb469..d18802d701e84 100644
--- a/apps/meteor/client/views/omnichannel/triggers/TriggersPage.tsx
+++ b/apps/meteor/client/views/omnichannel/triggers/TriggersPage.tsx
@@ -1,5 +1,5 @@
import { Button } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarDialog, Page, PageHeader, PageContent } from '@rocket.chat/ui-client';
import { useRouteParameter, useRouter } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -13,7 +13,7 @@ const TriggersPage = () => {
const id = useRouteParameter('id');
const context = useRouteParameter('context');
const router = useRouter();
- const handleClose = useEffectEvent(() => router.navigate('/omnichannel/triggers'));
+ const handleClose = useStableCallback(() => router.navigate('/omnichannel/triggers'));
return (
diff --git a/apps/meteor/client/views/omnichannel/triggers/TriggersRow.tsx b/apps/meteor/client/views/omnichannel/triggers/TriggersRow.tsx
index fbf7431cfab0c..f2b9f8666c8c5 100644
--- a/apps/meteor/client/views/omnichannel/triggers/TriggersRow.tsx
+++ b/apps/meteor/client/views/omnichannel/triggers/TriggersRow.tsx
@@ -1,6 +1,6 @@
import type { ILivechatTrigger } from '@rocket.chat/core-typings';
import { IconButton } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal, GenericTableCell, GenericTableRow } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useRoute, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import type { KeyboardEvent, MouseEvent } from 'react';
@@ -15,14 +15,14 @@ const TriggersRow = ({ _id, name, description, enabled, reload }: TriggersRowPro
const deleteTrigger = useEndpoint('DELETE', '/v1/livechat/triggers/:_id', { _id });
const dispatchToastMessage = useToastMessageDispatch();
- const handleClick = useEffectEvent(() => {
+ const handleClick = useStableCallback(() => {
triggersRoute.push({
context: 'edit',
id: _id,
});
});
- const handleKeyDown = useEffectEvent((e: KeyboardEvent) => {
+ const handleKeyDown = useStableCallback((e: KeyboardEvent) => {
if (!['Enter', 'Space'].includes(e.code)) {
return;
}
@@ -30,7 +30,7 @@ const TriggersRow = ({ _id, name, description, enabled, reload }: TriggersRowPro
handleClick();
});
- const handleDelete = useEffectEvent((e: MouseEvent) => {
+ const handleDelete = useStableCallback((e: MouseEvent) => {
e.stopPropagation();
const onDeleteTrigger = async () => {
try {
diff --git a/apps/meteor/client/views/omnichannel/triggers/TriggersTable.tsx b/apps/meteor/client/views/omnichannel/triggers/TriggersTable.tsx
index 1770d14094777..5153cf2322581 100644
--- a/apps/meteor/client/views/omnichannel/triggers/TriggersTable.tsx
+++ b/apps/meteor/client/views/omnichannel/triggers/TriggersTable.tsx
@@ -1,5 +1,5 @@
import { Pagination } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableHeader,
@@ -21,7 +21,7 @@ const TriggersTable = () => {
const t = useTranslation();
const router = useRouter();
- const handleAddNew = useEffectEvent(() => {
+ const handleAddNew = useStableCallback(() => {
router.navigate('/omnichannel/triggers/new');
});
diff --git a/apps/meteor/client/views/omnichannel/units/UnitTableRow.tsx b/apps/meteor/client/views/omnichannel/units/UnitTableRow.tsx
index a97bf405c6484..b62e0ea71bb85 100644
--- a/apps/meteor/client/views/omnichannel/units/UnitTableRow.tsx
+++ b/apps/meteor/client/views/omnichannel/units/UnitTableRow.tsx
@@ -1,5 +1,5 @@
import { IconButton } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericTableCell, GenericTableRow } from '@rocket.chat/ui-client';
import { useRouter } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -10,7 +10,7 @@ const UnitsTableRow = ({ _id, name, visibility }: { _id: string; name: string; v
const { t } = useTranslation();
const router = useRouter();
- const onRowClick = useEffectEvent((id: string) => () => router.navigate(`/omnichannel/units/edit/${id}`));
+ const onRowClick = useStableCallback((id: string) => () => router.navigate(`/omnichannel/units/edit/${id}`));
const handleDelete = useRemoveUnit(_id);
return (
diff --git a/apps/meteor/client/views/omnichannel/units/UnitsPage.tsx b/apps/meteor/client/views/omnichannel/units/UnitsPage.tsx
index 1bcd3c2eed990..771993a8b534e 100644
--- a/apps/meteor/client/views/omnichannel/units/UnitsPage.tsx
+++ b/apps/meteor/client/views/omnichannel/units/UnitsPage.tsx
@@ -1,5 +1,5 @@
import { Button, ButtonGroup } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ContextualbarDialog, Page, PageHeader, PageContent } from '@rocket.chat/ui-client';
import { useTranslation, useRouteParameter, useRouter } from '@rocket.chat/ui-contexts';
@@ -14,7 +14,7 @@ const UnitsPage = () => {
const context = useRouteParameter('context');
const id = useRouteParameter('id');
- const handleCloseContextualbar = useEffectEvent(() => router.navigate('/omnichannel/units'));
+ const handleCloseContextualbar = useStableCallback(() => router.navigate('/omnichannel/units'));
return (
diff --git a/apps/meteor/client/views/omnichannel/units/UnitsTable.tsx b/apps/meteor/client/views/omnichannel/units/UnitsTable.tsx
index 0fc76f74e684f..f2dda5654ebff 100644
--- a/apps/meteor/client/views/omnichannel/units/UnitsTable.tsx
+++ b/apps/meteor/client/views/omnichannel/units/UnitsTable.tsx
@@ -1,5 +1,5 @@
import { Pagination } from '@rocket.chat/fuselage';
-import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
GenericTable,
GenericTableHeader,
@@ -49,7 +49,7 @@ const UnitsTable = () => {
const [defaultQuery] = useState(hashKey([query]));
const queryHasChanged = defaultQuery !== hashKey([query]);
- const handleAddNew = useEffectEvent(() => router.navigate('/omnichannel/units/new'));
+ const handleAddNew = useStableCallback(() => router.navigate('/omnichannel/units/new'));
const headers = (
<>
diff --git a/apps/meteor/client/views/omnichannel/units/useRemoveUnit.tsx b/apps/meteor/client/views/omnichannel/units/useRemoveUnit.tsx
index 4beea2ebb5326..c5ca368d85dd4 100644
--- a/apps/meteor/client/views/omnichannel/units/useRemoveUnit.tsx
+++ b/apps/meteor/client/views/omnichannel/units/useRemoveUnit.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useTranslation, useRouter, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -12,7 +12,7 @@ export const useRemoveUnit = (id: string) => {
const queryClient = useQueryClient();
const removeUnit = useEndpoint('DELETE', '/v1/livechat/units/:id', { id });
- const handleDelete = useEffectEvent(() => {
+ const handleDelete = useStableCallback(() => {
const onDeleteAgent = async () => {
try {
await removeUnit();
diff --git a/apps/meteor/client/views/omnichannel/webhooks/WebhooksPage.tsx b/apps/meteor/client/views/omnichannel/webhooks/WebhooksPage.tsx
index fc4c73f3c2cb0..9b351228eb297 100644
--- a/apps/meteor/client/views/omnichannel/webhooks/WebhooksPage.tsx
+++ b/apps/meteor/client/views/omnichannel/webhooks/WebhooksPage.tsx
@@ -12,7 +12,7 @@ import {
NumberInput,
FieldLabel,
} from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ExternalLink, Page, PageHeader, PageScrollableContentWithShadow } from '@rocket.chat/ui-client';
import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
@@ -117,7 +117,7 @@ const WebhooksPage = ({ settings }: WebhooksPageProps) => {
[t],
);
- const handleSave = useEffectEvent(async (values: WebhooksPageFormData) => {
+ const handleSave = useStableCallback(async (values: WebhooksPageFormData) => {
const { sendOn, Livechat_webhookUrl, Livechat_secret_token, Livechat_http_timeout } = values;
try {
await save({
diff --git a/apps/meteor/client/views/room/Header/Omnichannel/BackButton.tsx b/apps/meteor/client/views/room/Header/Omnichannel/BackButton.tsx
index b883247938878..1146c29920a80 100644
--- a/apps/meteor/client/views/room/Header/Omnichannel/BackButton.tsx
+++ b/apps/meteor/client/views/room/Header/Omnichannel/BackButton.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { HeaderToolbarAction } from '@rocket.chat/ui-client';
import { useRouter } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -9,7 +9,7 @@ const BackButton = ({ routeName }: BackButtonProps) => {
const router = useRouter();
const { t } = useTranslation();
- const back = useEffectEvent(() => {
+ const back = useStableCallback(() => {
switch (routeName) {
case 'omnichannel-directory':
router.navigate({
diff --git a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/QuickActionOptions.tsx b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/QuickActionOptions.tsx
index 2c4bb57ca5026..bc45331f5652c 100644
--- a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/QuickActionOptions.tsx
+++ b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/QuickActionOptions.tsx
@@ -17,7 +17,7 @@ type QuickActionOptionsProps = {
const QuickActionOptions = ({ options, room, action, icon, ...props }: QuickActionOptionsProps) => {
const { t } = useTranslation();
- const reference = useRef(null);
+ const reference = useRef(null);
const target = useRef(null);
const { isVisible, toggle } = useDropdownVisibility({ reference, target });
diff --git a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx
index 91856b9758677..27dc3273cca0d 100644
--- a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx
+++ b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
useSetModal,
useToastMessageDispatch,
@@ -50,7 +50,7 @@ export const useQuickActions = (): {
const getVisitorInfo = useEndpoint('GET', '/v1/livechat/visitors.info');
- const getVisitorEmail = useEffectEvent(async () => {
+ const getVisitorEmail = useStableCallback(async () => {
if (!visitorRoomId) {
return;
}
@@ -194,7 +194,7 @@ export const useQuickActions = (): {
},
});
- const handleAction = useEffectEvent(async (id: string) => {
+ const handleAction = useStableCallback(async (id: string) => {
switch (id) {
case QuickActionsEnum.MoveQueue:
setModal(
@@ -314,7 +314,7 @@ export const useQuickActions = (): {
})
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
- const actionDefault = useEffectEvent((actionId: string) => {
+ const actionDefault = useStableCallback((actionId: string) => {
handleAction(actionId);
});
diff --git a/apps/meteor/client/views/room/Header/RoomTitle.tsx b/apps/meteor/client/views/room/Header/RoomTitle.tsx
index 531def26c09c3..716eb2dc59f5e 100644
--- a/apps/meteor/client/views/room/Header/RoomTitle.tsx
+++ b/apps/meteor/client/views/room/Header/RoomTitle.tsx
@@ -1,5 +1,5 @@
import { isTeamRoom, type IRoom } from '@rocket.chat/core-typings';
-import { useButtonPattern, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useButtonPattern, useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useDocumentTitle, HeaderTitle, HeaderTitleButton } from '@rocket.chat/ui-client';
import { useRoomToolbox } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -14,7 +14,7 @@ const RoomTitle = ({ room }: RoomTitleProps) => {
useDocumentTitle(room.name, false);
const { openTab } = useRoomToolbox();
- const handleOpenRoomInfo = useEffectEvent(() => {
+ const handleOpenRoomInfo = useStableCallback(() => {
if (isTeamRoom(room)) {
return openTab('team-info');
}
diff --git a/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx b/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx
index e1c796eca9cd1..b9d99f9c992c4 100644
--- a/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx
+++ b/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolbox.tsx
@@ -1,5 +1,5 @@
import type { Box } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericMenu, HeaderToolbarAction, HeaderToolbarDivider } from '@rocket.chat/ui-client';
import { useRoomToolbox, type RenderToolboxItemParams, type RoomToolboxActionConfig } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
@@ -20,7 +20,7 @@ const RoomToolbox = ({ className }: RoomToolboxProps) => {
const showKebabMenu = hiddenActions.length > 0;
- const renderDefaultToolboxItem = useEffectEvent(
+ const renderDefaultToolboxItem = useStableCallback(
({ id, className, icon, title, toolbox: { tab }, action, disabled, tooltip }: RenderToolboxItemParams) => {
return (
{
+ const handleFavoriteClick = useStableCallback(() => {
if (!isFavoritesEnabled) {
return;
}
diff --git a/apps/meteor/client/views/room/MessageList/MessageList.spec.tsx b/apps/meteor/client/views/room/MessageList/MessageList.spec.tsx
index 60adcfba4c9a3..f2bbdd50f5748 100644
--- a/apps/meteor/client/views/room/MessageList/MessageList.spec.tsx
+++ b/apps/meteor/client/views/room/MessageList/MessageList.spec.tsx
@@ -18,15 +18,15 @@ const mockVirtualizerHandle = {
};
jest.mock('virtua', () => {
- const React = jest.requireActual('react');
+ const { forwardRef, useImperativeHandle } = jest.requireActual('react');
return {
- VList: React.forwardRef(
+ VList: forwardRef(
(
{ children, onScroll, shift: _shift, ...props }: { children: ReactNode; onScroll?: (offset: number) => void; shift?: boolean },
ref: any,
) => {
- React.useImperativeHandle(ref, () => mockVirtualizerHandle);
+ useImperativeHandle(ref, () => mockVirtualizerHandle);
return (
onScroll?.(mockVirtualizerHandle.scrollOffset)} {...props}>
{children}
diff --git a/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx b/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx
index 94313e6925432..0bcbbccece740 100644
--- a/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx
+++ b/apps/meteor/client/views/room/MessageList/hooks/useMessageBody.tsx
@@ -31,7 +31,11 @@ export const useMessageBody = (message: IMessage | undefined): string | Root =>
}
if (message.attachments) {
- const attachment = message.attachments.find((attachment) => attachment.title);
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
+
+ if (attachment?.description) {
+ return attachment.description;
+ }
if (attachment?.title) {
return attachment.title;
diff --git a/apps/meteor/client/views/room/RoomAnnouncement/RoomAnnouncement.tsx b/apps/meteor/client/views/room/RoomAnnouncement/RoomAnnouncement.tsx
index 312541691cda2..71315843c19e4 100644
--- a/apps/meteor/client/views/room/RoomAnnouncement/RoomAnnouncement.tsx
+++ b/apps/meteor/client/views/room/RoomAnnouncement/RoomAnnouncement.tsx
@@ -1,5 +1,5 @@
import { Box } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { AnnouncementBanner, GenericModal } from '@rocket.chat/ui-client';
import { useSetModal } from '@rocket.chat/ui-contexts';
import type { KeyboardEvent, MouseEvent } from 'react';
@@ -15,7 +15,7 @@ const RoomAnnouncement = ({ announcement }: RoomAnnouncementParams) => {
const { t } = useTranslation();
const setModal = useSetModal();
- const handleOpenAnnouncement = useEffectEvent(() => {
+ const handleOpenAnnouncement = useStableCallback(() => {
setModal(
{
+ const invalidateQueries = useStableCallback(() => {
const reference = room.federationOriginalName ?? room.name ?? room._id;
void queryClient.invalidateQueries({ queryKey: roomsQueryKeys.room(room._id) });
void queryClient.invalidateQueries({ queryKey: subscriptionsQueryKeys.subscription(room._id) });
diff --git a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx
index 3b7f7106d8165..3b0c80db3351f 100644
--- a/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx
+++ b/apps/meteor/client/views/room/UserCard/UserCardWithData.tsx
@@ -1,6 +1,6 @@
import { getUserDisplayName } from '@rocket.chat/core-typings';
import type { IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericMenu } from '@rocket.chat/ui-client';
import { useSetting, useRolesDescription } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
@@ -67,7 +67,7 @@ const UserCardWithData = ({ username, rid, onOpenUserInfo, onClose }: UserCardWi
};
}, [data, username, showRealNames, isLoading, getRoles]);
- const handleOpenUserInfo = useEffectEvent(() => {
+ const handleOpenUserInfo = useStableCallback(() => {
onOpenUserInfo();
onClose();
});
diff --git a/apps/meteor/client/views/room/body/DropTargetOverlay.tsx b/apps/meteor/client/views/room/body/DropTargetOverlay.tsx
index 6238c4553e201..4b2299a666975 100644
--- a/apps/meteor/client/views/room/body/DropTargetOverlay.tsx
+++ b/apps/meteor/client/views/room/body/DropTargetOverlay.tsx
@@ -1,6 +1,6 @@
import { css } from '@rocket.chat/css-in-js';
import { Box } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { DragEvent, ReactNode } from 'react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -18,12 +18,12 @@ type DropTargetOverlayProps = {
function DropTargetOverlay({ enabled, reason, onFileDrop, visible = true, onDismiss }: DropTargetOverlayProps) {
const { t } = useTranslation();
- const handleDragLeave = useEffectEvent((event: DragEvent) => {
+ const handleDragLeave = useStableCallback((event: DragEvent) => {
event.stopPropagation();
onDismiss?.();
});
- const handleDragOver = useEffectEvent((event: DragEvent) => {
+ const handleDragOver = useStableCallback((event: DragEvent) => {
event.stopPropagation();
event.preventDefault();
@@ -32,7 +32,7 @@ function DropTargetOverlay({ enabled, reason, onFileDrop, visible = true, onDism
const formatDateAndTime = useFormatDateAndTime();
- const handleDrop = useEffectEvent(async (event: DragEvent) => {
+ const handleDrop = useStableCallback(async (event: DragEvent) => {
event.stopPropagation();
onDismiss?.();
diff --git a/apps/meteor/client/views/room/body/hooks/useFileUploadDropTarget.ts b/apps/meteor/client/views/room/body/hooks/useFileUploadDropTarget.ts
index c4aedcadb940f..33d8b07536005 100644
--- a/apps/meteor/client/views/room/body/hooks/useFileUploadDropTarget.ts
+++ b/apps/meteor/client/views/room/body/hooks/useFileUploadDropTarget.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { usePermission, useSetting, useTranslation, useUser } from '@rocket.chat/ui-contexts';
import type { DragEvent, ReactNode } from 'react';
import { useMemo, useSyncExternalStore } from 'react';
@@ -44,7 +44,7 @@ export const useFileUploadDropTarget = (): readonly [
chat?.composer?.editing.get ?? (() => false),
);
- const onFileDrop = useEffectEvent(async (files: File[]) => {
+ const onFileDrop = useStableCallback(async (files: File[]) => {
const { getMimeType } = await import('../../../../../app/utils/lib/mimeTypes');
const getUniqueFiles = () => {
const uniqueFiles: File[] = [];
diff --git a/apps/meteor/client/views/room/composer/hooks/useComposerBoxPopup.ts b/apps/meteor/client/views/room/composer/hooks/useComposerBoxPopup.ts
index 399e77d41e568..16889f6ad0b58 100644
--- a/apps/meteor/client/views/room/composer/hooks/useComposerBoxPopup.ts
+++ b/apps/meteor/client/views/room/composer/hooks/useComposerBoxPopup.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { UseQueryResult } from '@tanstack/react-query';
import type { MutableRefObject } from 'react';
import { useEffect, useCallback, useState, useRef } from 'react';
@@ -87,7 +87,7 @@ export const useComposerBoxPopup = (
});
}, [items, option, suspended]);
- const select = useEffectEvent((item: T) => {
+ const select = useStableCallback((item: T) => {
if (!option) {
throw new Error('No popup is open');
}
@@ -114,7 +114,7 @@ export const useComposerBoxPopup = (
setFocused(undefined);
});
- const setOptionByInput = useEffectEvent((): ComposerBoxPopupOptions | undefined => {
+ const setOptionByInput = useStableCallback((): ComposerBoxPopupOptions | undefined => {
const value = chat?.composer?.substring(0, chat?.composer?.selection.start);
if (!value) {
@@ -150,14 +150,14 @@ export const useComposerBoxPopup = (
return option;
});
- const handleFocus = useEffectEvent(() => {
+ const handleFocus = useStableCallback(() => {
if (option) {
return;
}
setOptionByInput();
});
- const handleKeyUp = useEffectEvent((event: KeyboardEvent) => {
+ const handleKeyUp = useStableCallback((event: KeyboardEvent) => {
if (!setOptionByInput()) {
return;
}
@@ -174,7 +174,7 @@ export const useComposerBoxPopup = (
}
});
- const handleKeyDown = useEffectEvent((event: KeyboardEvent) => {
+ const handleKeyDown = useStableCallback((event: KeyboardEvent) => {
if (!option) {
return;
}
@@ -230,7 +230,7 @@ export const useComposerBoxPopup = (
}
});
- const clear = useEffectEvent(() => {
+ const clear = useStableCallback(() => {
if (!option) {
return;
}
diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx
index 05305c9a3f6a5..ca2baa22c1b7b 100644
--- a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx
+++ b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx
@@ -1,6 +1,6 @@
/* eslint-disable complexity */
import { isRoomFederated, isRoomNativeFederated, type IMessage, type ISubscription } from '@rocket.chat/core-typings';
-import { useContentBoxSize, useEffectEvent, useMediaQuery, useSafeRefCallback } from '@rocket.chat/fuselage-hooks';
+import { useContentBoxSize, useStableCallback, useMediaQuery, useSafeRefCallback } from '@rocket.chat/fuselage-hooks';
import {
MessageComposerAction,
MessageComposerToolbarActions,
@@ -125,7 +125,7 @@ const MessageBox = ({
throw new Error('Chat context not found');
}
- const textareaRef = useRef(null);
+ const textareaRef = useRef(null);
const messageComposerRef = useRef(null);
const subscription = useRoomSubscription();
@@ -153,7 +153,7 @@ const MessageBox = ({
const useEmojis = useUserPreference('useEmojis');
- const handleOpenEmojiPicker = useEffectEvent((e: MouseEvent) => {
+ const handleOpenEmojiPicker = useStableCallback((e: MouseEvent) => {
e.stopPropagation();
e.preventDefault();
@@ -167,7 +167,7 @@ const MessageBox = ({
const { hasUploads, handleUploadFiles, isUploading, isProcessingUploads } = useFileUpload();
- const handleSendMessage = useEffectEvent(() => {
+ const handleSendMessage = useStableCallback(() => {
if (isUploading || isProcessingUploads) {
return;
}
@@ -203,7 +203,7 @@ const MessageBox = ({
}
};
- const keyboardEventHandler = useEffectEvent((event: KeyboardEvent) => {
+ const keyboardEventHandler = useStableCallback((event: KeyboardEvent) => {
const { which: keyCode } = event;
const input = event.target as HTMLTextAreaElement;
@@ -329,7 +329,7 @@ const MessageBox = ({
mutationFn: async () => onJoin?.(),
});
- const handlePaste = useEffectEvent((event: ClipboardEvent) => {
+ const handlePaste = useStableCallback((event: ClipboardEvent) => {
const { clipboardData } = event;
if (!clipboardData) {
diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useAudioMessageAction.ts b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useAudioMessageAction.ts
index 24e8722773676..0fcf04040ea63 100644
--- a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useAudioMessageAction.ts
+++ b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useAudioMessageAction.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { useSetting } from '@rocket.chat/ui-contexts';
import { useEffect, useMemo } from 'react';
@@ -34,13 +34,13 @@ export const useAudioMessageAction = (disabled: boolean, isMicrophoneDenied: boo
const chat = useChat();
- const stopRecording = useEffectEvent(() => {
+ const stopRecording = useStableCallback(() => {
chat?.action.stop('recording');
chat?.composer?.setRecordingMode(false);
});
- const setMicrophoneDenied = useEffectEvent((isDenied: boolean) => {
+ const setMicrophoneDenied = useStableCallback((isDenied: boolean) => {
if (isDenied) {
stopRecording();
}
diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useVideoMessageAction.ts b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useVideoMessageAction.ts
index 46d72c824b4e1..69ab7f9479813 100644
--- a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useVideoMessageAction.ts
+++ b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useVideoMessageAction.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { useSetting } from '@rocket.chat/ui-contexts';
import { useEffect, useMemo } from 'react';
@@ -40,7 +40,7 @@ export const useVideoMessageAction = (disabled: boolean): GenericMenuItemProps =
}
};
- const handleDenyVideo = useEffectEvent((isDenied: boolean) => {
+ const handleDenyVideo = useStableCallback((isDenied: boolean) => {
if (isDenied) {
chat?.composer?.setRecordingVideo(false);
}
diff --git a/apps/meteor/client/views/room/composer/messageBox/hooks/useMediaPermissions.ts b/apps/meteor/client/views/room/composer/messageBox/hooks/useMediaPermissions.ts
index 1e0e5421566b0..4c7596e44e5c5 100644
--- a/apps/meteor/client/views/room/composer/messageBox/hooks/useMediaPermissions.ts
+++ b/apps/meteor/client/views/room/composer/messageBox/hooks/useMediaPermissions.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useQuery, useQueryClient } from '@tanstack/react-query';
type MediaDevices = 'camera' | 'microphone';
@@ -17,7 +17,7 @@ export const useMediaPermissions = (name: MediaDevices): [isPermissionDenied: bo
const queryKey = ['media-permissions', name];
- const setIsPermissionDenied = useEffectEvent((isDenied: boolean) => {
+ const setIsPermissionDenied = useStableCallback((isDenied: boolean) => {
queryClient.setQueryData(queryKey, isDenied);
});
diff --git a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx
index 78f6674e8dd27..2087793ef5836 100644
--- a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx
+++ b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useLanguage, useToastMessageDispatch, useRoomToolbox } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { ChangeEvent } from 'react';
@@ -31,7 +31,7 @@ const AutoTranslateWithData = () => {
const languagesDict = supportedLanguages ? Object.fromEntries(supportedLanguages.map((lang) => [lang.language, lang.name])) : {};
- const handleChangeLanguage = useEffectEvent(async (value: string) => {
+ const handleChangeLanguage = useStableCallback(async (value: string) => {
setCurrentLanguage(value);
await saveSettings({
@@ -45,7 +45,7 @@ const AutoTranslateWithData = () => {
});
});
- const handleSwitch = useEffectEvent(async (event: ChangeEvent) => {
+ const handleSwitch = useStableCallback(async (event: ChangeEvent) => {
await saveSettings({
roomId: room._id,
field: 'autoTranslate',
diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/useDownloadExportMutation.ts b/apps/meteor/client/views/room/contextualBar/ExportMessages/useDownloadExportMutation.ts
index 291475075f4e4..f7b53352df5a1 100644
--- a/apps/meteor/client/views/room/contextualBar/ExportMessages/useDownloadExportMutation.ts
+++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/useDownloadExportMutation.ts
@@ -39,6 +39,7 @@ export const useDownloadExportMutation = () => {
...('image_type' in attachment && { image_type: attachment.image_type }),
...('image_size' in attachment && { image_size: attachment.image_size }),
...('type' in attachment && { type: attachment.type }),
+ description: attachment.description,
})) ?? [],
}),
);
diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/useExportMessagesAsPDFMutation.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/useExportMessagesAsPDFMutation.tsx
index 383388f0c316f..716f7f3c8bd19 100644
--- a/apps/meteor/client/views/room/contextualBar/ExportMessages/useExportMessagesAsPDFMutation.tsx
+++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/useExportMessagesAsPDFMutation.tsx
@@ -116,6 +116,7 @@ export const useExportMessagesAsPDFMutation = () => {
{parseMessage(message)}
{message.attachments?.map((attachment: MessageAttachmentDefault, index) => (
+ {attachment.description && {attachment.description}}
{attachment.image_url && }
{attachment.title}
diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx
index 5a3c71a902562..d99dbb8e8208e 100644
--- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx
+++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx
@@ -22,7 +22,7 @@ import {
TextAreaInput,
AccordionItem,
} from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
ContextualbarHeader,
ContextualbarBack,
@@ -152,7 +152,7 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) =>
const handleArchive = useArchiveRoom(room);
// TODO: add payload validation
- const handleUpdateRoomData = useEffectEvent(
+ const handleUpdateRoomData = useStableCallback(
async ({
hideSysMes,
joinCodeRequired,
@@ -195,7 +195,7 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) =>
},
);
- const handleSave = useEffectEvent((data: EditRoomInfoFormData) =>
+ const handleSave = useStableCallback((data: EditRoomInfoFormData) =>
Promise.all([isDirty && handleUpdateRoomData(data), changeArchiving && handleArchive()].filter(Boolean)),
);
diff --git a/apps/meteor/client/views/room/contextualBar/Info/RoomInfoRouter.tsx b/apps/meteor/client/views/room/contextualBar/Info/RoomInfoRouter.tsx
index 543a08a672507..247f568f13571 100644
--- a/apps/meteor/client/views/room/contextualBar/Info/RoomInfoRouter.tsx
+++ b/apps/meteor/client/views/room/contextualBar/Info/RoomInfoRouter.tsx
@@ -1,5 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRoomToolbox } from '@rocket.chat/ui-contexts';
import { useState } from 'react';
@@ -21,7 +21,7 @@ const RoomInfoRouter = ({ onClickBack, onEnterRoom, resetState }: RoomInfoRouter
const room = useRoom();
const canEdit = useCanEditRoom(room);
- const onClickEnterRoom = useEffectEvent(() => onEnterRoom?.(room));
+ const onClickEnterRoom = useStableCallback(() => onEnterRoom?.(room));
if (isEditing) {
return setIsEditing(false)} />;
diff --git a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomConvertToTeam.tsx b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomConvertToTeam.tsx
index 9e8984c060302..911ef93428773 100644
--- a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomConvertToTeam.tsx
+++ b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomConvertToTeam.tsx
@@ -1,6 +1,6 @@
import { isRoomFederated } from '@rocket.chat/core-typings';
import type { IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useTranslation, useEndpoint, usePermission } from '@rocket.chat/ui-contexts';
@@ -18,7 +18,7 @@ export const useRoomConvertToTeam = (room: IRoom) => {
const convertRoomToTeam = useEndpoint('POST', room.t === 'c' ? '/v1/channels.convertToTeam' : '/v1/groups.convertToTeam');
- const handleConvertToTeam = useEffectEvent(async () => {
+ const handleConvertToTeam = useStableCallback(async () => {
const onConfirm = async () => {
try {
await convertRoomToTeam(room.t === 'c' ? { channelId: room._id } : { roomId: room._id });
diff --git a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomLeave.tsx b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomLeave.tsx
index 4b87fd73229e2..7bd475dbfc57a 100644
--- a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomLeave.tsx
+++ b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomLeave.tsx
@@ -1,5 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useRouter, useSetModal, useToastMessageDispatch, useEndpoint, usePermission, useUserSubscription } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -21,7 +21,7 @@ export const useRoomLeave = (room: IRoom) => {
const canLeave = usePermission(room.t === 'c' ? 'leave-c' : 'leave-p') && room.cl !== false && Boolean(subscription);
- const handleLeave = useEffectEvent(() => {
+ const handleLeave = useStableCallback(() => {
const leaveAction = async () => {
try {
if (room.t === 'c') {
diff --git a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomMoveToTeam.tsx b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomMoveToTeam.tsx
index 1e6403ac90adf..30b25ebc9e9df 100644
--- a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomMoveToTeam.tsx
+++ b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomMoveToTeam.tsx
@@ -1,6 +1,6 @@
import { isRoomFederated } from '@rocket.chat/core-typings';
import type { IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import ChannelToTeamModal from '../../ChannelToTeamModal';
@@ -16,7 +16,7 @@ export const useRoomMoveToTeam = (room: IRoom) => {
const moveChannelToTeam = useEndpoint('POST', '/v1/teams.addRooms');
- const handleMoveToTeam = useEffectEvent(async () => {
+ const handleMoveToTeam = useStableCallback(async () => {
const onConfirm = async (teamId: IRoom['teamId']) => {
if (!teamId) {
throw new Error('teamId not provided');
diff --git a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearchForm.tsx b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearchForm.tsx
index c7bf80efcd7a8..36306d14779a1 100644
--- a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearchForm.tsx
+++ b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearchForm.tsx
@@ -1,6 +1,6 @@
import type { IMessageSearchProvider } from '@rocket.chat/core-typings';
import { Box, Field, FieldLabel, FieldHint, Icon, TextInput, ToggleSwitch, Callout } from '@rocket.chat/fuselage';
-import { useDebouncedCallback, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useDebouncedCallback, useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import DOMPurify from 'dompurify';
import { useEffect, useId } from 'react';
@@ -31,7 +31,7 @@ const MessageSearchForm = ({ provider, onSearch, searchListId, isSuccess }: Mess
setFocus('searchText');
}, [setFocus]);
- const debouncedOnSearch = useDebouncedCallback(useEffectEvent(onSearch), 300);
+ const debouncedOnSearch = useDebouncedCallback(useStableCallback(onSearch), 300);
const submitHandler = handleSubmit(({ searchText, globalSearch }) => {
debouncedOnSearch.cancel();
diff --git a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx
index 4bc37c930a935..6d82da1146491 100644
--- a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx
+++ b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx
@@ -1,5 +1,5 @@
import { isDirectMessageRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useEndpoint, useRoomToolbox } from '@rocket.chat/ui-contexts';
import { useCallback, useMemo, useState } from 'react';
@@ -67,7 +67,7 @@ const PruneMessagesWithData = () => {
return new Date(`${olderDate || '9999-12-31'}T${olderTime || '23:59'}:59${getTimeZoneOffset()}`);
}, [olderDate, olderTime]);
- const handlePrune = useEffectEvent((): void => {
+ const handlePrune = useStableCallback((): void => {
const handlePruneAction = async () => {
const limit = DEFAULT_PRUNE_LIMIT;
diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useDeleteFile.tsx b/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useDeleteFile.tsx
index 8f556969bc28d..bde721a6b269e 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useDeleteFile.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useDeleteFile.tsx
@@ -1,5 +1,5 @@
import type { IUpload } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useMethod } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -10,7 +10,7 @@ export const useDeleteFile = (reload: () => void) => {
const dispatchToastMessage = useToastMessageDispatch();
const deleteFile = useMethod('deleteFileMessage');
- const handleDelete = useEffectEvent((_id: IUpload['_id']) => {
+ const handleDelete = useStableCallback((_id: IUpload['_id']) => {
const onConfirm = async () => {
try {
await deleteFile(_id);
diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddMatrixUsers/useAddMatrixUsers.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddMatrixUsers/useAddMatrixUsers.tsx
index 41b0f7b2e95ab..ad344046a8ada 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddMatrixUsers/useAddMatrixUsers.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddMatrixUsers/useAddMatrixUsers.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
@@ -12,7 +12,7 @@ export type useAddMatrixUsersProps = {
export const useAddMatrixUsers = () => {
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
- const handleClose = useEffectEvent(() => setModal(null));
+ const handleClose = useStableCallback(() => setModal(null));
const dispatchVerifyEndpoint = useEndpoint('GET', '/v1/federation/matrixIds.verify');
return useMutation({
diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.tsx
index b934f9d1a52be..7fe089bd155f8 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.tsx
@@ -1,7 +1,7 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { isRoomFederated, isRoomNativeFederated } from '@rocket.chat/core-typings';
import { Field, FieldError, FieldLabel, Button, ButtonGroup, FieldGroup } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
ContextualbarHeader,
ContextualbarBack,
@@ -53,7 +53,7 @@ const AddUsers = ({ rid, onClickBack, reload }: AddUsersProps) => {
formState: { isDirty, isSubmitting, errors },
} = useForm({ defaultValues: { users: [] } });
- const handleSave = useEffectEvent(async ({ users, unbanConfirmed }: { users: string[]; unbanConfirmed?: boolean }) => {
+ const handleSave = useStableCallback(async ({ users, unbanConfirmed }: { users: string[]; unbanConfirmed?: boolean }) => {
if (unbanConfirmed) {
const { bannedUsers } = await getBannedUsers({ roomId: rid });
const bannedSet = new Set(bannedUsers.map((u) => u.username));
@@ -71,7 +71,7 @@ const AddUsers = ({ rid, onClickBack, reload }: AddUsersProps) => {
reload();
});
- const handleSaveWithBannedCheck = useEffectEvent(async ({ users }: { users: string[] }) => {
+ const handleSaveWithBannedCheck = useStableCallback(async ({ users }: { users: string[] }) => {
try {
await handleSave({ users });
} catch (error: any) {
diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/InviteUsersWithData.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/InviteUsersWithData.tsx
index 3b9a96a09499a..1e45cbc382611 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/InviteUsersWithData.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/InviteUsersWithData.tsx
@@ -1,5 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useTranslation, useToastMessageDispatch, useRoomToolbox } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { useState, useEffect } from 'react';
@@ -33,10 +33,10 @@ const InviteUsersWithData = ({ rid, onClickBack }: InviteUsersWithDataProps) =>
const format = useFormatDateAndTime();
const findOrCreateInvite = useEndpoint('POST', '/v1/findOrCreateInvite');
- const handleEdit = useEffectEvent(() => setInviteState((prevState) => ({ ...prevState, isEditing: true })));
- const handleBackToLink = useEffectEvent(() => setInviteState((prevState) => ({ ...prevState, isEditing: false })));
+ const handleEdit = useStableCallback(() => setInviteState((prevState) => ({ ...prevState, isEditing: true })));
+ const handleBackToLink = useStableCallback(() => setInviteState((prevState) => ({ ...prevState, isEditing: false })));
- const linkExpirationText = useEffectEvent(
+ const linkExpirationText = useStableCallback(
(data?: {
days: number;
maxUses: number;
@@ -86,7 +86,7 @@ const InviteUsersWithData = ({ rid, onClickBack }: InviteUsersWithDataProps) =>
}
}, [dispatchToastMessage, isSuccess, t]);
- const handleGenerateLink = useEffectEvent((daysAndMaxUses: { days: string; maxUses: string }) => {
+ const handleGenerateLink = useStableCallback((daysAndMaxUses: { days: string; maxUses: string }) => {
setInviteState((prevState) => ({ ...prevState, daysAndMaxUses, isEditing: false }));
});
diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx
index f1c77cdce6b47..e80ffe39ef535 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx
@@ -15,7 +15,7 @@ import {
ContextualbarDialog,
} from '@rocket.chat/ui-client';
import { useSetting } from '@rocket.chat/ui-contexts';
-import type { FormEventHandler, ComponentProps, MouseEvent, ElementType } from 'react';
+import type { ChangeEventHandler, ComponentProps, MouseEvent, ElementType } from 'react';
import { useId, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { GroupedVirtuoso } from 'react-virtuoso';
@@ -34,7 +34,7 @@ type RoomMembersProps = {
isSuccess: boolean;
text: string;
type: string;
- setText: FormEventHandler;
+ setText: ChangeEventHandler;
setType: (type: 'online' | 'all') => void;
members: RoomMember[];
total: number;
diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx
index f22c6035d8532..e9ed78d9bf3af 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembersWithData.tsx
@@ -1,6 +1,6 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { isRoomFederated, isDirectMessageRoom, isTeamRoom, isRoomNativeFederated } from '@rocket.chat/core-typings';
-import { useEffectEvent, useDebouncedValue, useLocalStorage } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useDebouncedValue, useLocalStorage } from '@rocket.chat/fuselage-hooks';
import {
useUserRoom,
useAtLeastOnePermission,
@@ -73,7 +73,7 @@ const RoomMembersWithData = ({ rid }: { rid: IRoom['_id'] }) => {
setText(event.currentTarget.value);
}, []);
- const openUserInfo = useEffectEvent((e: MouseEvent) => {
+ const openUserInfo = useStableCallback((e: MouseEvent) => {
const { userid: userId, invitationdate: invitationDate } = e.currentTarget.dataset;
setState({
tab: ROOM_MEMBERS_TABS.INFO,
@@ -81,15 +81,15 @@ const RoomMembersWithData = ({ rid }: { rid: IRoom['_id'] }) => {
});
});
- const openInvite = useEffectEvent(() => {
+ const openInvite = useStableCallback(() => {
setState({ tab: ROOM_MEMBERS_TABS.INVITE });
});
- const openAddUser = useEffectEvent(() => {
+ const openAddUser = useStableCallback(() => {
setState({ tab: ROOM_MEMBERS_TABS.ADD });
});
- const handleBack = useEffectEvent(() => {
+ const handleBack = useStableCallback(() => {
setState({ tab: ROOM_MEMBERS_TABS.LIST });
});
diff --git a/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx b/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx
index 6b9ad90565726..50c983828c43d 100644
--- a/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx
+++ b/apps/meteor/client/views/room/contextualBar/Threads/ThreadList.tsx
@@ -13,7 +13,7 @@ import {
ContextualbarDialog,
} from '@rocket.chat/ui-client';
import { useTranslation, useUserId, useRoomToolbox } from '@rocket.chat/ui-contexts';
-import type { FormEvent } from 'react';
+import type { ChangeEvent } from 'react';
import { useMemo, useState, useCallback, useId } from 'react';
import { Virtuoso } from 'react-virtuoso';
@@ -46,7 +46,7 @@ const ThreadList = () => {
const [searchText, setSearchText] = useState('');
const handleSearchTextChange = useCallback(
- (event: FormEvent) => {
+ (event: ChangeEvent) => {
setSearchText(event.currentTarget.value);
},
[setSearchText],
diff --git a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useNormalizedThreadTitleHtml.ts b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useNormalizedThreadTitleHtml.ts
index c052942a9567d..4ac708211cf47 100644
--- a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useNormalizedThreadTitleHtml.ts
+++ b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useNormalizedThreadTitleHtml.ts
@@ -33,7 +33,11 @@ export const useNormalizedThreadTitleHtml = (mainMessage: IThreadMainMessage) =>
}
if (message.attachments) {
- const attachment = message.attachments.find((attachment) => attachment.title);
+ const attachment = message.attachments.find((attachment) => attachment.title || attachment.description);
+
+ if (attachment?.description) {
+ return escapeHTML(attachment.description);
+ }
if (attachment?.title) {
return escapeHTML(attachment.title);
diff --git a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListItem.tsx b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListItem.tsx
index 071bba9fd1df3..4a8f9a77fb84f 100644
--- a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListItem.tsx
+++ b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfList/VideoConfListItem.tsx
@@ -16,7 +16,7 @@ import {
ButtonGroup,
AvatarStack,
} from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { UserAvatar } from '@rocket.chat/ui-avatar';
import { useUserDisplayName } from '@rocket.chat/ui-client';
import { useTranslation } from '@rocket.chat/ui-contexts';
@@ -62,7 +62,7 @@ const VideoConfListItem = ({
}
`;
- const handleJoinConference = useEffectEvent((): void => {
+ const handleJoinConference = useStableCallback((): void => {
joinCall(callId);
return reload();
});
diff --git a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/IncomingPopup.tsx b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/IncomingPopup.tsx
index 6199b909a1fe8..9533c76fbc333 100644
--- a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/IncomingPopup.tsx
+++ b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/IncomingPopup.tsx
@@ -1,6 +1,6 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { Skeleton } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import {
useVideoConfSetPreferences,
@@ -45,7 +45,7 @@ const IncomingPopup = ({ id, room, position, onClose, onMute, onConfirm }: Incom
const showMic = Boolean(data?.capabilities?.mic);
const showCam = Boolean(data?.capabilities?.cam);
- const handleJoinCall = useEffectEvent(() => {
+ const handleJoinCall = useStableCallback(() => {
setPreferences(controllersConfig);
onConfirm();
});
diff --git a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/StartCallPopup.tsx b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/StartCallPopup.tsx
index 6fccd1935c55a..a1124140edfc4 100644
--- a/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/StartCallPopup.tsx
+++ b/apps/meteor/client/views/room/contextualBar/VideoConference/VideoConfPopups/VideoConfPopup/StartCallPopup.tsx
@@ -1,5 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
-import { useOutsideClick, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useOutsideClick, useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
VideoConfPopup,
VideoConfPopupHeader,
@@ -46,7 +46,7 @@ const StartCallPopup = ({ id, loading, room, onClose, onConfirm }: StartCallPopu
const showCam = !!capabilities.cam;
const showMic = !!capabilities.mic;
- const handleStartCall = useEffectEvent(() => {
+ const handleStartCall = useStableCallback(() => {
setPreferences(controllersConfig);
onConfirm();
});
diff --git a/apps/meteor/client/views/room/contextualBar/VideoConference/hooks/useVideoConfWarning.tsx b/apps/meteor/client/views/room/contextualBar/VideoConference/hooks/useVideoConfWarning.tsx
index 08e7b9ecb4621..6ca59eec0fca5 100644
--- a/apps/meteor/client/views/room/contextualBar/VideoConference/hooks/useVideoConfWarning.tsx
+++ b/apps/meteor/client/views/room/contextualBar/VideoConference/hooks/useVideoConfWarning.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useRoute, useRole } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
@@ -8,9 +8,9 @@ export const useVideoConfWarning = (): ((error: unknown) => void) => {
const setModal = useSetModal();
const isAdmin = useRole('admin');
const videoConfSettingsRoute = useRoute('admin-settings');
- const handleClose = useEffectEvent(() => setModal(null));
+ const handleClose = useStableCallback(() => setModal(null));
- const handleRedirectToConfiguration = useEffectEvent(() => {
+ const handleRedirectToConfiguration = useStableCallback(() => {
handleClose();
videoConfSettingsRoute.push({
group: 'Video_Conference',
diff --git a/apps/meteor/client/views/room/contextualBar/uikit/UiKitContextualBar.tsx b/apps/meteor/client/views/room/contextualBar/uikit/UiKitContextualBar.tsx
index 334d88f713fbf..40f39b628f657 100644
--- a/apps/meteor/client/views/room/contextualBar/uikit/UiKitContextualBar.tsx
+++ b/apps/meteor/client/views/room/contextualBar/uikit/UiKitContextualBar.tsx
@@ -1,5 +1,5 @@
import { Avatar, Box, Button, ButtonGroup } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
UiKitComponent,
UiKitContextualBar as UiKitContextualBarSurfaceRender,
@@ -40,7 +40,7 @@ const UiKitContextualBar = ({ initialView }: UiKitContextualBarProps) => {
const { closeTab } = useRoomToolbox();
- const handleSubmit = useEffectEvent((e: FormEvent) => {
+ const handleSubmit = useStableCallback((e: FormEvent) => {
preventSyntheticEvent(e);
closeTab();
void actionManager.emitInteraction(view.appId, {
@@ -56,7 +56,7 @@ const UiKitContextualBar = ({ initialView }: UiKitContextualBarProps) => {
});
});
- const handleCancel = useEffectEvent((e: UIEvent) => {
+ const handleCancel = useStableCallback((e: UIEvent) => {
preventSyntheticEvent(e);
closeTab();
void actionManager.emitInteraction(view.appId, {
@@ -73,7 +73,7 @@ const UiKitContextualBar = ({ initialView }: UiKitContextualBarProps) => {
});
});
- const handleClose = useEffectEvent((e: UIEvent) => {
+ const handleClose = useStableCallback((e: UIEvent) => {
preventSyntheticEvent(e);
closeTab();
void actionManager.emitInteraction(view.appId, {
diff --git a/apps/meteor/client/views/room/hooks/useGoToThread.ts b/apps/meteor/client/views/room/hooks/useGoToThread.ts
index 80e21e31972dc..cedfa9bf643f7 100644
--- a/apps/meteor/client/views/room/hooks/useGoToThread.ts
+++ b/apps/meteor/client/views/room/hooks/useGoToThread.ts
@@ -1,12 +1,12 @@
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRouter } from '@rocket.chat/ui-contexts';
export const useGoToThread = ({ replace = false }: { replace?: boolean } = {}) => {
const router = useRouter();
// TODO: remove params recycling
- return useEffectEvent(({ rid, tmid, msg }: { rid: IRoom['_id']; tmid: IMessage['_id']; msg?: IMessage['_id'] }) => {
+ return useStableCallback(({ rid, tmid, msg }: { rid: IRoom['_id']; tmid: IMessage['_id']; msg?: IMessage['_id'] }) => {
const routeName = router.getRouteName();
if (!routeName) {
diff --git a/apps/meteor/client/views/room/hooks/useGoToThreadList.ts b/apps/meteor/client/views/room/hooks/useGoToThreadList.ts
index 67c87a2faf8d9..e381abed21d20 100644
--- a/apps/meteor/client/views/room/hooks/useGoToThreadList.ts
+++ b/apps/meteor/client/views/room/hooks/useGoToThreadList.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRouter } from '@rocket.chat/ui-contexts';
import { useRoom } from '../contexts/RoomContext';
@@ -7,7 +7,7 @@ export const useGoToThreadList = ({ replace = false }: { replace?: boolean } = {
const router = useRouter();
const room = useRoom();
- return useEffectEvent(() => {
+ return useStableCallback(() => {
const routeName = router.getRouteName();
if (!routeName) {
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx
index 0d40c18957009..9768701f6493a 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAddUserAction.tsx
@@ -1,6 +1,6 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { isRoomFederated, isRoomNativeFederated } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
useTranslation,
useUser,
@@ -57,7 +57,7 @@ export const useAddUserAction = (
const inviteUser = useEndpoint('POST', inviteUserEndpoints[room.t === 'p' ? 'p' : 'c']);
- const handleAddUser = useEffectEvent(async ({ users }: { users: string[] }) => {
+ const handleAddUser = useStableCallback(async ({ users }: { users: string[] }) => {
const [username] = users;
await inviteUser({ roomId: rid, username });
reload?.();
@@ -65,7 +65,7 @@ export const useAddUserAction = (
const addClickHandler = useAddMatrixUsers();
- const addUserOptionAction = useEffectEvent(async () => {
+ const addUserOptionAction = useStableCallback(async () => {
try {
const users = [username as string];
if (isRoomFederated(room)) {
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useBlockUserAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useBlockUserAction.ts
index e4b798c681607..d2c4f1e065e5a 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useBlockUserAction.ts
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useBlockUserAction.ts
@@ -1,5 +1,5 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation, useMethod, useToastMessageDispatch, useUserId, useUserSubscription, useUserRoom } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
@@ -23,7 +23,7 @@ export const useBlockUserAction = (user: Pick, rid: I
const isUserBlocked = currentSubscription?.blocker;
const toggleBlock = useMethod(isUserBlocked ? 'unblockUser' : 'blockUser');
- const toggleBlockUserAction = useEffectEvent(async () => {
+ const toggleBlockUserAction = useStableCallback(async () => {
try {
await toggleBlock({ rid, blocked: uid });
dispatchToastMessage({
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts
index 354f8ae70b52a..ee95763fb193b 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts
@@ -1,5 +1,5 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
useTranslation,
usePermission,
@@ -53,7 +53,7 @@ export const useChangeLeaderAction = (user: Pick, rid
},
});
- const changeLeaderAction = useEffectEvent(async () => toggleOwnerMutation.mutateAsync({ roomId: rid, userId: uid }));
+ const changeLeaderAction = useStableCallback(async () => toggleOwnerMutation.mutateAsync({ roomId: rid, userId: uid }));
const changeLeaderOption = useMemo(
() =>
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx
index 3eb855fa4acfa..a914b29e15308 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx
@@ -1,6 +1,6 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { isRoomFederated, isRoomNativeFederated } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { escapeHTML } from '@rocket.chat/string-helpers';
import { GenericModal } from '@rocket.chat/ui-client';
import {
@@ -140,7 +140,7 @@ export const useChangeModeratorAction = (user: Pick,
[setModal, loggedUserId, loggedUserIsModerator, loggedUserIsOwner, t, rid, uid, toggleModerator, room],
);
- const changeModeratorAction = useEffectEvent(() => handleChangeModerator({ userId: uid }));
+ const changeModeratorAction = useStableCallback(() => handleChangeModerator({ userId: uid }));
const roomIsFederated = isRoomFederated(room);
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx
index 92279afd4ab06..b588e3488626d 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx
@@ -1,6 +1,6 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { isRoomFederated, isRoomNativeFederated } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { escapeHTML } from '@rocket.chat/string-helpers';
import { GenericModal } from '@rocket.chat/ui-client';
import {
@@ -128,7 +128,7 @@ export const useChangeOwnerAction = (user: Pick, rid:
toggleOwnerMutation.mutateAsync({ roomId: rid, userId: uid });
}, [room, loggedUserId, loggedUserIsOwner, toggleOwnerMutation, rid, uid, t, setModal]);
- const changeOwnerAction = useEffectEvent(async () => handleChangeOwner());
+ const changeOwnerAction = useStableCallback(async () => handleChangeOwner());
const roomIsFederated = isRoomFederated(room);
const isFederationBlocked = room && !isRoomNativeFederated(room);
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts
index 82f75c0f93d07..4c2f9bf825396 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts
@@ -1,5 +1,5 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import {
useTranslation,
useUserSubscription,
@@ -30,7 +30,7 @@ export const useIgnoreUserAction = (user: Pick, rid:
const { roomCanIgnore } = getRoomDirectives({ room, showingUserId: uid, userSubscription: currentSubscription });
- const ignoreUserAction = useEffectEvent(async () => {
+ const ignoreUserAction = useStableCallback(async () => {
try {
await ignoreUser({ rid, userId: uid, ignore: String(!isIgnored) });
if (isIgnored) {
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useMuteUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useMuteUserAction.tsx
index b5d49be770210..d160f5f1089f6 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useMuteUserAction.tsx
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useMuteUserAction.tsx
@@ -1,5 +1,5 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { escapeHTML } from '@rocket.chat/string-helpers';
import { GenericModal } from '@rocket.chat/ui-client';
import {
@@ -44,7 +44,7 @@ export const useMuteUserAction = (user: Pick, rid: IR
const userCanMute = usePermission('mute-user', rid);
const dispatchToastMessage = useToastMessageDispatch();
const setModal = useSetModal();
- const closeModal = useEffectEvent(() => setModal(null));
+ const closeModal = useStableCallback(() => setModal(null));
const otherUserCanPostReadonly = useAllPermissions(
useMemo(() => ['post-readonly'], []),
rid,
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx
index 28c3d6252f4f8..9e8fc983b733e 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx
@@ -1,6 +1,6 @@
import type { IRoom, IUser, Serialized } from '@rocket.chat/core-typings';
import { isRoomFederated, isRoomNativeFederated } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { escapeHTML } from '@rocket.chat/string-helpers';
import { GenericModal } from '@rocket.chat/ui-client';
import {
@@ -51,7 +51,7 @@ export const useRemoveUserAction = (
? !isFederationBlocked && Federation.isEditableByTheUser(currentUser || undefined, room, subscription)
: hasPermissionToRemove;
const setModal = useSetModal();
- const closeModal = useEffectEvent(() => setModal(null));
+ const closeModal = useStableCallback(() => setModal(null));
const roomName = room?.t && escapeHTML(roomCoordinator.getRoomName(room.t, room));
const { roomCanRemove } = getRoomDirectives({ room, showingUserId: uid, userSubscription: subscription });
@@ -80,7 +80,7 @@ export const useRemoveUserAction = (
},
});
- const removeUserOptionAction = useEffectEvent(() => {
+ const removeUserOptionAction = useStableCallback(() => {
const handleRemoveFromTeam = async (rooms: Record>) => {
if (room.teamId) {
const roomKeys = Object.keys(rooms);
diff --git a/apps/meteor/client/views/room/modals/E2EEModals/BaseDisableE2EEModal.tsx b/apps/meteor/client/views/room/modals/E2EEModals/BaseDisableE2EEModal.tsx
index 16c617240698b..7f250f1c1a6c0 100644
--- a/apps/meteor/client/views/room/modals/E2EEModals/BaseDisableE2EEModal.tsx
+++ b/apps/meteor/client/views/room/modals/E2EEModals/BaseDisableE2EEModal.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useState } from 'react';
import DisableE2EEModal from './DisableE2EEModal';
@@ -20,7 +20,7 @@ type BaseDisableE2EEModalProps = {
const BaseDisableE2EEModal = ({ onConfirm, onClose, roomType, roomId, canResetRoomKey }: BaseDisableE2EEModalProps) => {
const [step, setStep] = useState(STEPS.DISABLE_E2EE);
- const onResetRoomKey = useEffectEvent(() => {
+ const onResetRoomKey = useStableCallback(() => {
setStep(STEPS.RESET_ROOM_KEY);
});
diff --git a/apps/meteor/client/views/room/providers/RoomToolboxProvider.tsx b/apps/meteor/client/views/room/providers/RoomToolboxProvider.tsx
index 766620e25bdc7..d3d505b55f22b 100644
--- a/apps/meteor/client/views/room/providers/RoomToolboxProvider.tsx
+++ b/apps/meteor/client/views/room/providers/RoomToolboxProvider.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent, useStableArray } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useStableArray } from '@rocket.chat/fuselage-hooks';
import {
useUserId,
useSetting,
@@ -24,7 +24,7 @@ const RoomToolboxProvider = ({ children }: RoomToolboxProviderProps) => {
const router = useRouter();
- const openTab = useEffectEvent((actionId: string, context?: string) => {
+ const openTab = useStableCallback((actionId: string, context?: string) => {
if (actionId === tab?.id && context === undefined) {
return closeTab();
}
@@ -48,7 +48,7 @@ const RoomToolboxProvider = ({ children }: RoomToolboxProviderProps) => {
});
});
- const closeTab = useEffectEvent(() => {
+ const closeTab = useStableCallback(() => {
const routeName = router.getRouteName();
if (!routeName) {
diff --git a/apps/meteor/client/views/room/providers/UserCardProvider.tsx b/apps/meteor/client/views/room/providers/UserCardProvider.tsx
index 0b6df8f3e6f4a..58d5a1c467a20 100644
--- a/apps/meteor/client/views/room/providers/UserCardProvider.tsx
+++ b/apps/meteor/client/views/room/providers/UserCardProvider.tsx
@@ -1,7 +1,7 @@
import { useOverlayTrigger } from '@react-aria/overlays';
import { useOverlayTriggerState } from '@react-stately/overlays';
import { Popover } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRoomToolbox, UserCardContext } from '@rocket.chat/ui-contexts';
import type { ComponentProps, ReactNode, UIEvent } from 'react';
import { Suspense, lazy, useCallback, useMemo, useRef, useState } from 'react';
@@ -21,7 +21,7 @@ const UserCardProvider = ({ children }: { children: ReactNode }) => {
const { openTab } = useRoomToolbox();
- const openUserInfo = useEffectEvent((username?: string) => {
+ const openUserInfo = useStableCallback((username?: string) => {
switch (room.t) {
case 'l':
openTab('room-info', username);
diff --git a/apps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsx b/apps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsx
index 1415b80fbbfa6..aa5624c1d21fe 100644
--- a/apps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsx
+++ b/apps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsx
@@ -1,7 +1,7 @@
import type { IWebdavNode, IWebdavAccountIntegration } from '@rocket.chat/core-typings';
import type { SelectOption } from '@rocket.chat/fuselage';
import { Modal, Box, IconButton, Select, ModalHeader, ModalTitle, ModalClose, ModalContent, ModalFooter } from '@rocket.chat/fuselage';
-import { useEffectEvent, useDebouncedValue } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import { useSort } from '@rocket.chat/ui-client';
import { useMethod, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import type { MouseEvent } from 'react';
@@ -36,7 +36,7 @@ const WebdavFilePickerModal = ({ onUpload, onClose, account }: WebdavFilePickerM
const debouncedFilter = useDebouncedValue('', 500);
const [isLoading, setIsLoading] = useState(false);
- const showFilePreviews = useEffectEvent(async (accountId: string, nodes: (IWebdavNode & { preview?: string })[] | undefined) => {
+ const showFilePreviews = useStableCallback(async (accountId: string, nodes: (IWebdavNode & { preview?: string })[] | undefined) => {
if (!Array.isArray(nodes) || !nodes.length) {
return;
}
diff --git a/apps/meteor/client/views/root/hooks/loggedIn/useNotificationUserCalendar.ts b/apps/meteor/client/views/root/hooks/loggedIn/useNotificationUserCalendar.ts
index 8bf576c35629a..930628c90888b 100644
--- a/apps/meteor/client/views/root/hooks/loggedIn/useNotificationUserCalendar.ts
+++ b/apps/meteor/client/views/root/hooks/loggedIn/useNotificationUserCalendar.ts
@@ -1,5 +1,5 @@
import type { ICalendarNotification, IUser } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { imperativeModal } from '@rocket.chat/ui-client';
import { useStream, useUserPreference } from '@rocket.chat/ui-contexts';
import { useEffect } from 'react';
@@ -10,7 +10,7 @@ export const useNotificationUserCalendar = (user: IUser) => {
const requireInteraction = useUserPreference('desktopNotificationRequireInteraction');
const notifyUserStream = useStream('notify-user');
- const notifyUserCalendar = useEffectEvent(async (notification: ICalendarNotification) => {
+ const notifyUserCalendar = useStableCallback(async (notification: ICalendarNotification) => {
if (user.status === 'busy') {
return;
}
diff --git a/apps/meteor/client/views/root/hooks/loggedIn/useNotifyUser.ts b/apps/meteor/client/views/root/hooks/loggedIn/useNotifyUser.ts
index 6fcec028341e2..0a4f8fafb4298 100644
--- a/apps/meteor/client/views/root/hooks/loggedIn/useNotifyUser.ts
+++ b/apps/meteor/client/views/root/hooks/loggedIn/useNotifyUser.ts
@@ -1,5 +1,5 @@
import type { AtLeast, INotificationDesktop, ISubscription, IUser } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useEmbeddedLayout } from '@rocket.chat/ui-client';
import { useCustomSound, useRouter, useStream, useUserPreference } from '@rocket.chat/ui-contexts';
import { useEffect } from 'react';
@@ -18,7 +18,7 @@ export const useNotifyUser = (user: IUser) => {
const newMessageNotification = useNewMessageNotification();
const showDesktopNotification = useDesktopNotification();
- const notifyNewRoom = useEffectEvent(async (sub: AtLeast): Promise => {
+ const notifyNewRoom = useStableCallback(async (sub: AtLeast): Promise => {
if (user.status === 'busy') {
return;
}
@@ -28,7 +28,7 @@ export const useNotifyUser = (user: IUser) => {
}
});
- const notifyNewMessageAudioAndDesktop = useEffectEvent((notification: INotificationDesktop) => {
+ const notifyNewMessageAudioAndDesktop = useStableCallback((notification: INotificationDesktop) => {
const hasFocus = document.hasFocus();
const openedRoomId = ['channel', 'group', 'direct'].includes(router.getRouteName() || '') ? RoomManager.opened : undefined;
diff --git a/apps/meteor/client/views/root/hooks/loggedIn/useRootUrlChange.tsx b/apps/meteor/client/views/root/hooks/loggedIn/useRootUrlChange.tsx
index cde0f3daf3627..89a5c82f58df7 100644
--- a/apps/meteor/client/views/root/hooks/loggedIn/useRootUrlChange.tsx
+++ b/apps/meteor/client/views/root/hooks/loggedIn/useRootUrlChange.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRole, useSetModal, useSetting, useSettingSetValue, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useMutation } from '@tanstack/react-query';
import { useEffect } from 'react';
@@ -12,7 +12,7 @@ export const useRootUrlChange = () => {
const dispatchToastMessage = useToastMessageDispatch();
const isAdmin = useRole('admin');
const setModal = useSetModal();
- const closeModal = useEffectEvent(() => setModal(null));
+ const closeModal = useStableCallback(() => setModal(null));
const currentUrl = location.origin + getRootUrlPathPrefix();
const siteUrl = useSetting('Site_Url', '');
diff --git a/apps/meteor/client/views/root/hooks/useIframe.ts b/apps/meteor/client/views/root/hooks/useIframe.ts
index b66b9f247fc36..fdbe80fb2012f 100644
--- a/apps/meteor/client/views/root/hooks/useIframe.ts
+++ b/apps/meteor/client/views/root/hooks/useIframe.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useLoginWithIframe, useLoginWithToken, useSetting } from '@rocket.chat/ui-contexts';
import { useCallback, useEffect, useState } from 'react';
@@ -34,7 +34,7 @@ export const useIframe = () => {
[iframeLogin, tokenLogin],
);
- const tryLogin = useEffectEvent(async (callback?: (error: Error | null | undefined, result: unknown) => void) => {
+ const tryLogin = useStableCallback(async (callback?: (error: Error | null | undefined, result: unknown) => void) => {
if (!enabled) {
return;
}
diff --git a/apps/meteor/client/views/root/hooks/useNotificationPermission.ts b/apps/meteor/client/views/root/hooks/useNotificationPermission.ts
index e5e3faeb8ee71..ee1d43428a28d 100644
--- a/apps/meteor/client/views/root/hooks/useNotificationPermission.ts
+++ b/apps/meteor/client/views/root/hooks/useNotificationPermission.ts
@@ -1,9 +1,9 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { notificationManager } from '../../../lib/notificationManager';
export const useNotificationPermission = () => {
- const requestPermission = useEffectEvent(async () => {
+ const requestPermission = useStableCallback(async () => {
const response = await Notification.requestPermission();
notificationManager.allowed = response === 'granted';
notificationManager.emit('change');
diff --git a/apps/meteor/client/views/teams/ChannelDesertionTable/ChannelDesertionTable.tsx b/apps/meteor/client/views/teams/ChannelDesertionTable/ChannelDesertionTable.tsx
index 61e6ca129e201..ec0bea58e17c6 100644
--- a/apps/meteor/client/views/teams/ChannelDesertionTable/ChannelDesertionTable.tsx
+++ b/apps/meteor/client/views/teams/ChannelDesertionTable/ChannelDesertionTable.tsx
@@ -39,7 +39,20 @@ const ChannelDesertionTable = ({
const direction = sortDirection === 'asc' ? 1 : -1;
- return rooms.sort((a, b) => (a[sortBy] && b[sortBy] ? (a[sortBy]?.localeCompare(b[sortBy] ?? '') ?? 1) * direction : direction));
+ return rooms.toSorted((a, b) => {
+ const aValue = a[sortBy] ?? '';
+ const bValue = b[sortBy] ?? '';
+ if (!aValue && !bValue) {
+ return 0;
+ }
+ if (!aValue) {
+ return 1;
+ }
+ if (!bValue) {
+ return -1;
+ }
+ return aValue.localeCompare(bValue) * direction;
+ });
}, [rooms, sortBy, sortDirection]);
return (
diff --git a/apps/meteor/client/views/teams/ChannelDesertionTable/ChannelDesertionTableRow.tsx b/apps/meteor/client/views/teams/ChannelDesertionTable/ChannelDesertionTableRow.tsx
index ea16a70fb4475..6e412ac5fe2d1 100644
--- a/apps/meteor/client/views/teams/ChannelDesertionTable/ChannelDesertionTableRow.tsx
+++ b/apps/meteor/client/views/teams/ChannelDesertionTable/ChannelDesertionTableRow.tsx
@@ -1,6 +1,6 @@
import type { IRoom, Serialized } from '@rocket.chat/core-typings';
import { CheckBox, Icon, Margins } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericTableRow, GenericTableCell } from '@rocket.chat/ui-client';
import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime';
@@ -15,7 +15,7 @@ type ChannelDesertionTableRowProps = {
const ChannelDesertionTableRow = ({ room, onChange, selected, lastOwnerWarning }: ChannelDesertionTableRowProps) => {
const { name, fname, ts, isLastOwner } = room;
const formatDate = useFormatDateAndTime();
- const handleChange = useEffectEvent(() => onChange(room));
+ const handleChange = useStableCallback(() => onChange(room));
return (
diff --git a/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannels.tsx b/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannels.tsx
index 7b153154cb951..6603b802006fd 100644
--- a/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannels.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannels.tsx
@@ -1,7 +1,7 @@
import type { IRoom } from '@rocket.chat/core-typings';
import type { SelectOption } from '@rocket.chat/fuselage';
import { Box, Icon, TextInput, Select, Throbber, ButtonGroup, Button } from '@rocket.chat/fuselage';
-import { useEffectEvent, useAutoFocus, useDebouncedCallback } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useAutoFocus, useDebouncedCallback } from '@rocket.chat/fuselage-hooks';
import {
VirtualizedScrollbars,
ContextualbarHeader,
@@ -66,7 +66,7 @@ const TeamsChannels = ({
[t],
);
- const lm = useEffectEvent(() => !loading && loadMoreItems());
+ const lm = useStableCallback(() => !loading && loadMoreItems());
const loadMoreChannels = useDebouncedCallback(
() => {
diff --git a/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelsWithData.tsx b/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelsWithData.tsx
index 65c9a32a34e5e..73358e825ac10 100644
--- a/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelsWithData.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/channels/TeamsChannelsWithData.tsx
@@ -1,5 +1,5 @@
import type { IRoom } from '@rocket.chat/core-typings';
-import { useLocalStorage, useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useLocalStorage, useDebouncedValue, useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, usePermission, useAtLeastOnePermission, useRoomToolbox } from '@rocket.chat/ui-contexts';
import type { ChangeEvent } from 'react';
import { useCallback, useState } from 'react';
@@ -34,15 +34,15 @@ const TeamsChannelsWithData = () => {
setText(event.currentTarget.value);
}, []);
- const handleAddExisting = useEffectEvent(() => {
+ const handleAddExisting = useStableCallback(() => {
setModal( setModal(null)} reload={refetch} />);
});
- const handleCreateNew = useEffectEvent(() => {
+ const handleCreateNew = useStableCallback(() => {
setModal( setModal(null)} reload={refetch} />);
});
- const goToRoom = useEffectEvent((room: IRoom) => {
+ const goToRoom = useStableCallback((room: IRoom) => {
roomCoordinator.openRouteLink(room.t, room);
});
diff --git a/apps/meteor/client/views/teams/contextualBar/info/ConvertToChannelModal/BaseConvertToChannelModal.tsx b/apps/meteor/client/views/teams/contextualBar/info/ConvertToChannelModal/BaseConvertToChannelModal.tsx
index d692c0dd91049..c6011ea9a4ae3 100644
--- a/apps/meteor/client/views/teams/contextualBar/info/ConvertToChannelModal/BaseConvertToChannelModal.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/info/ConvertToChannelModal/BaseConvertToChannelModal.tsx
@@ -1,5 +1,5 @@
import type { IRoom, Serialized } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useState, useCallback } from 'react';
import FirstStep from './ModalSteps/FirstStep';
@@ -28,8 +28,8 @@ const BaseConvertToChannelModal = ({
const [step, setStep] = useState(currentStep);
const [selectedRooms, setSelectedRooms] = useState<{ [key: string]: Serialized }>({});
- const onContinue = useEffectEvent(() => setStep(STEPS.CONFIRM_CONVERT));
- const onReturn = useEffectEvent(() => setStep(STEPS.LIST_ROOMS));
+ const onContinue = useStableCallback(() => setStep(STEPS.CONFIRM_CONVERT));
+ const onReturn = useStableCallback(() => setStep(STEPS.LIST_ROOMS));
const eligibleRooms = rooms;
@@ -43,7 +43,7 @@ const BaseConvertToChannelModal = ({
});
}, []);
- const onToggleAllRooms = useEffectEvent(() => {
+ const onToggleAllRooms = useStableCallback(() => {
if (Object.values(selectedRooms).filter(Boolean).length === 0 && eligibleRooms) {
return setSelectedRooms(Object.fromEntries(eligibleRooms.map((room) => [room._id, room])));
}
diff --git a/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/ChannelDeletionTable/ChannelDeletionTableRow.tsx b/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/ChannelDeletionTable/ChannelDeletionTableRow.tsx
index 827dad67f9877..2a28a97d4b66f 100644
--- a/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/ChannelDeletionTable/ChannelDeletionTableRow.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/ChannelDeletionTable/ChannelDeletionTableRow.tsx
@@ -1,6 +1,6 @@
import type { IRoom, Serialized } from '@rocket.chat/core-typings';
import { CheckBox, Margins } from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { GenericTableRow, GenericTableCell } from '@rocket.chat/ui-client';
import { RoomIcon } from '../../../../../../components/RoomIcon';
@@ -13,7 +13,7 @@ type ChannelDeletionTableRowProps = {
const ChannelDeletionTableRow = ({ room, onChange, selected }: ChannelDeletionTableRowProps) => {
const { name, fname, usersCount } = room;
- const handleChange = useEffectEvent(() => onChange(room));
+ const handleChange = useStableCallback(() => onChange(room));
return (
diff --git a/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/DeleteTeamModal.tsx b/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/DeleteTeamModal.tsx
index c8be4d1bb456f..c59a36a9a0bff 100644
--- a/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/DeleteTeamModal.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/info/DeleteTeam/DeleteTeamModal.tsx
@@ -1,5 +1,5 @@
import type { IRoom, Serialized } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useState } from 'react';
import DeleteTeamChannels from './DeleteTeamChannels';
@@ -20,7 +20,7 @@ const DeleteTeamModal = ({ onCancel, onConfirm, rooms }: DeleteTeamModalProps) =
const [deletedRooms, setDeletedRooms] = useState<{ [key: string]: Serialized }>({});
const [keptRooms, setKeptRooms] = useState<{ [key: string]: Serialized }>({});
- const onChangeRoomSelection = useEffectEvent((room: Serialized) => {
+ const onChangeRoomSelection = useStableCallback((room: Serialized) => {
if (deletedRooms[room._id]) {
setDeletedRooms((deletedRooms) => {
delete deletedRooms[room._id];
@@ -31,14 +31,14 @@ const DeleteTeamModal = ({ onCancel, onConfirm, rooms }: DeleteTeamModalProps) =
setDeletedRooms((deletedRooms) => ({ ...deletedRooms, [room._id]: room }));
});
- const onToggleAllRooms = useEffectEvent(() => {
+ const onToggleAllRooms = useStableCallback(() => {
if (Object.values(deletedRooms).filter(Boolean).length === 0) {
return setDeletedRooms(Object.fromEntries(rooms.map((room) => [room._id, room])));
}
setDeletedRooms({});
});
- const onSelectRooms = useEffectEvent(() => {
+ const onSelectRooms = useStableCallback(() => {
const keptRooms = Object.fromEntries(rooms.filter((room) => !deletedRooms[room._id]).map((room) => [room._id, room]));
setKeptRooms(keptRooms);
setStep(STEPS.CONFIRM_DELETE);
diff --git a/apps/meteor/client/views/teams/contextualBar/info/LeaveTeam/LeaveTeamModal/LeaveTeamModal.tsx b/apps/meteor/client/views/teams/contextualBar/info/LeaveTeam/LeaveTeamModal/LeaveTeamModal.tsx
index 2f67370a92e28..1dd9b7f4834f1 100644
--- a/apps/meteor/client/views/teams/contextualBar/info/LeaveTeam/LeaveTeamModal/LeaveTeamModal.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/info/LeaveTeam/LeaveTeamModal/LeaveTeamModal.tsx
@@ -1,5 +1,5 @@
import type { IRoom, Serialized } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useState, useCallback, useMemo } from 'react';
import LeaveTeamModalChannels from './LeaveTeamModalChannels';
@@ -36,7 +36,7 @@ const LeaveTeamModal = ({ rooms, onCancel, onConfirm }: LeaveTeamModalProps) =>
});
}, []);
- const onToggleAllRooms = useEffectEvent(() => {
+ const onToggleAllRooms = useStableCallback(() => {
setSelectedRooms((selectedRooms) => {
if (Object.values(selectedRooms).filter(Boolean).length === 0) {
return Object.fromEntries(rooms.filter(({ isLastOwner }) => !isLastOwner).map((room) => [room._id, room]));
diff --git a/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.tsx b/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.tsx
index d834e047cc228..00651b01569f8 100644
--- a/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { usePermission, useRoomToolbox } from '@rocket.chat/ui-contexts';
import { useCallback, useState } from 'react';
@@ -12,7 +12,7 @@ const TeamsInfoWithData = () => {
const { openTab, closeTab } = useRoomToolbox();
const canEdit = usePermission('edit-team-channel', room._id);
- const onClickBack = useEffectEvent(() => setEditing(false));
+ const onClickBack = useStableCallback(() => setEditing(false));
const onClickViewChannels = useCallback(() => openTab('team-channels'), [openTab]);
if (editing) {
diff --git a/apps/meteor/client/views/teams/contextualBar/info/useConvertToChannel.tsx b/apps/meteor/client/views/teams/contextualBar/info/useConvertToChannel.tsx
index 2136aa7517796..474ffd9045483 100644
--- a/apps/meteor/client/views/teams/contextualBar/info/useConvertToChannel.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/info/useConvertToChannel.tsx
@@ -1,5 +1,5 @@
import type { IRoom, Serialized } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { usePermission, useSetModal, useToastMessageDispatch, useUserId } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -22,7 +22,7 @@ export const useConvertToChannel = ({ _id, teamId }: IRoom) => {
},
});
- const onClickConvertToChannel = useEffectEvent(() => {
+ const onClickConvertToChannel = useStableCallback(() => {
if (!userId || !teamId) {
throw new Error('Invalid teamId or userId');
}
diff --git a/apps/meteor/client/views/teams/contextualBar/info/useLeaveTeam.tsx b/apps/meteor/client/views/teams/contextualBar/info/useLeaveTeam.tsx
index b1dd0c589910e..2bf45c060e094 100644
--- a/apps/meteor/client/views/teams/contextualBar/info/useLeaveTeam.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/info/useLeaveTeam.tsx
@@ -1,5 +1,5 @@
import type { IRoom, Serialized } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useRouter, useSetModal, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';
@@ -23,7 +23,7 @@ export const useLeaveTeam = ({ teamId }: IRoom) => {
});
// const canLeave = usePermission('leave-team'); /* && room.cl !== false && joined */
- const handleLeaveTeam = useEffectEvent(() => {
+ const handleLeaveTeam = useStableCallback(() => {
if (!teamId) {
throw new Error('Invalid teamId');
}
diff --git a/apps/meteor/client/views/teams/contextualBar/members/RemoveUsersModal/BaseRemoveUsersModal.tsx b/apps/meteor/client/views/teams/contextualBar/members/RemoveUsersModal/BaseRemoveUsersModal.tsx
index 17643cebcc3c0..17d2b1dfcf73f 100644
--- a/apps/meteor/client/views/teams/contextualBar/members/RemoveUsersModal/BaseRemoveUsersModal.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/members/RemoveUsersModal/BaseRemoveUsersModal.tsx
@@ -1,5 +1,5 @@
import type { IRoom, Serialized } from '@rocket.chat/core-typings';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { usePermission } from '@rocket.chat/ui-contexts';
import { useState, useCallback } from 'react';
@@ -32,8 +32,8 @@ const BaseRemoveUsersModal = ({
const [selectedRooms, setSelectedRooms] = useState & { isLastOwner?: boolean }>>({});
- const onContinue = useEffectEvent(() => setStep(STEPS.CONFIRM_DELETE));
- const onReturn = useEffectEvent(() => setStep(STEPS.LIST_ROOMS));
+ const onContinue = useStableCallback(() => setStep(STEPS.CONFIRM_DELETE));
+ const onReturn = useStableCallback(() => setStep(STEPS.LIST_ROOMS));
const canViewUserRooms = usePermission('view-all-team-channels');
@@ -49,7 +49,7 @@ const BaseRemoveUsersModal = ({
});
}, []);
- const onToggleAllRooms = useEffectEvent(() => {
+ const onToggleAllRooms = useStableCallback(() => {
if (Object.values(selectedRooms).filter(Boolean).length === 0) {
return setSelectedRooms(Object.fromEntries(eligibleRooms?.map((room) => [room._id, room]) ?? []));
}
diff --git a/apps/meteor/ee/server/api/abac/index.ts b/apps/meteor/ee/server/api/abac/index.ts
index 0f1abe77f290d..72629cb2a85d2 100644
--- a/apps/meteor/ee/server/api/abac/index.ts
+++ b/apps/meteor/ee/server/api/abac/index.ts
@@ -1,8 +1,8 @@
import { AbacAttributeStoreExternalError, getPdpHealthErrorCode } from '@rocket.chat/abac';
-import { Abac, LDAPEnterprise } from '@rocket.chat/core-services';
+import { Abac } from '@rocket.chat/core-services';
import type { AbacActor } from '@rocket.chat/core-services';
import type { IServerEvents, IUser } from '@rocket.chat/core-typings';
-import { ServerEvents, Users } from '@rocket.chat/models';
+import { ServerEvents } from '@rocket.chat/models';
import { validateUnauthorizedErrorResponse } from '@rocket.chat/rest-typings/src/v1/Ajv';
import { convertSubObjectsIntoPaths } from '@rocket.chat/tools';
@@ -209,7 +209,7 @@ const abacEndpoints = API.v1
{
authRequired: true,
permissionsRequired: ['abac-management', 'manage-abac-admin-room-attributes'],
- license: ['abac', 'ldap-enterprise'],
+ license: ['abac'],
body: POSTAbacUsersSyncBodySchema,
response: {
200: GenericSuccessSchema,
@@ -225,7 +225,7 @@ const abacEndpoints = API.v1
const { usernames, ids, emails, ldapIds } = this.bodyParams;
- await LDAPEnterprise.syncUsersAbacAttributes(Users.findUsersByIdentifiers({ usernames, ids, emails, ldapIds }));
+ await Abac.reevaluateUsers({ usernames, ids, emails, ldapIds });
return API.v1.success();
},
diff --git a/apps/meteor/ee/server/local-services/ldap/service.ts b/apps/meteor/ee/server/local-services/ldap/service.ts
index 1f756be0d48b8..cba54b832b044 100644
--- a/apps/meteor/ee/server/local-services/ldap/service.ts
+++ b/apps/meteor/ee/server/local-services/ldap/service.ts
@@ -1,5 +1,6 @@
import { ServiceClassInternal, type ILDAPEEService } from '@rocket.chat/core-services';
import type { IUser } from '@rocket.chat/core-typings';
+import { Users } from '@rocket.chat/models';
import type { FindCursor } from 'mongodb';
import { LDAPEEManager } from '../../lib/ldap/Manager';
@@ -30,4 +31,8 @@ export class LDAPEEService extends ServiceClassInternal implements ILDAPEEServic
async syncUsersAbacAttributes(users: FindCursor): Promise {
return LDAPEEManager.syncUsersAbacAttributes(users);
}
+
+ async syncUsersAbacAttributesByIds(userIds: string[]): Promise {
+ return LDAPEEManager.syncUsersAbacAttributes(Users.findUsersByIdentifiers({ ids: userIds }));
+ }
}
diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts
index d15ac6dbbc909..b2480acff247d 100644
--- a/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts
+++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Outgoing.ts
@@ -142,7 +142,11 @@ slashCommands.add({
return;
}
- const emailText = message?.msg || '';
+ const emailText =
+ message?.attachments
+ ?.map((a) => a.description)
+ .filter(Boolean)
+ .join('\n\n') || '';
void sendEmail(
inbox,
diff --git a/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts b/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts
index b2b47ad0b9228..088aff0c8b052 100644
--- a/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts
+++ b/apps/meteor/server/services/messages/hooks/BeforeSaveMarkdownParser.ts
@@ -38,6 +38,10 @@ export class BeforeSaveMarkdownParser {
if (message.msg) {
message.md = parse(message.msg, config);
}
+
+ if (message.attachments?.[0]?.description) {
+ message.attachments[0].descriptionMd = parse(message.attachments[0].description, config);
+ }
} catch (e) {
console.error(e); // errors logged while the parser is at experimental stage
}
diff --git a/apps/meteor/tests/end-to-end/api/abac.ts b/apps/meteor/tests/end-to-end/api/abac.ts
index 71a71131d684e..c0eaef4208b69 100644
--- a/apps/meteor/tests/end-to-end/api/abac.ts
+++ b/apps/meteor/tests/end-to-end/api/abac.ts
@@ -4,7 +4,7 @@ import { expect } from 'chai';
import { before, after, describe, it } from 'mocha';
import { MongoClient } from 'mongodb';
-import { getCredentials, request, credentials, methodCall } from '../../data/api-data';
+import { api, getCredentials, request, credentials, methodCall } from '../../data/api-data';
import { sleep } from '../../data/livechat/utils';
import {
mockServerHealthy,
@@ -190,7 +190,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
it('POST /abac/users/sync should return 403', async () => {
await request
- .post(`${v1}/abac/users/sync`)
+ .post(api('abac/users/sync'))
.set(credentials)
.send({ usernames: ['x'] })
.expect(403);
@@ -1451,6 +1451,17 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});
});
+ it('POST /abac/users/sync should fail with error-abac-not-enabled', async () => {
+ await request
+ .post(api('abac/users/sync'))
+ .set(credentials)
+ .send({ ids: ['no-such-user-id'] })
+ .expect(400)
+ .expect((res) => {
+ expect(res.body.error).to.include('error-abac-not-enabled');
+ });
+ });
+
after(async () => {
await updateSetting('ABAC_Enabled', true);
});
@@ -1832,6 +1843,27 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});
});
+ describe('POST /abac/users/sync (strategy-agnostic)', () => {
+ before(async () => {
+ await updateSetting('ABAC_Enabled', true);
+ });
+
+ after(async () => {
+ await updateSetting('ABAC_Enabled', false);
+ });
+
+ it('responds 200 with success:true when ABAC_Enabled=true and PDP type=local (no-match id)', async () => {
+ await request
+ .post(api('abac/users/sync'))
+ .set(credentials)
+ .send({ ids: ['no-such-user-id'] })
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ });
+ });
+ });
+
describe('Room access (invite, addition)', () => {
let roomWithoutAttr: IRoom;
let roomWithAttr: IRoom;
@@ -2503,7 +2535,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
it('should sync ABAC attributes for SOME users via /abac/users/sync', async () => {
await request
- .post(`${v1}/abac/users/sync`)
+ .post(api('abac/users/sync'))
.set(credentials)
.send({
usernames: ['david.scott', 'gene.cernan'],
@@ -2533,7 +2565,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
it('should fail /abac/users/sync when more than 100 usernames are provided', async () => {
const usernames = Array.from({ length: 101 }, (_, i) => `user_${i}@example.com`);
await request
- .post(`${v1}/abac/users/sync`)
+ .post(api('abac/users/sync'))
.set(credentials)
.send({
usernames,
@@ -2547,7 +2579,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
it('should fail /abac/users/sync when more than 100 ids are provided', async () => {
const ids = Array.from({ length: 101 }, (_, i) => `id_${i}`);
await request
- .post(`${v1}/abac/users/sync`)
+ .post(api('abac/users/sync'))
.set(credentials)
.send({
ids,
@@ -2561,7 +2593,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
it('should fail /abac/users/sync when more than 100 emails are provided', async () => {
const emails = Array.from({ length: 101 }, (_, i) => `user_${i}@example.com`);
await request
- .post(`${v1}/abac/users/sync`)
+ .post(api('abac/users/sync'))
.set(credentials)
.send({
emails,
@@ -2575,7 +2607,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
it('should fail /abac/users/sync when more than 100 ldapIds are provided', async () => {
const ldapIds = Array.from({ length: 101 }, (_, i) => `ldap_${i}`);
await request
- .post(`${v1}/abac/users/sync`)
+ .post(api('abac/users/sync'))
.set(credentials)
.send({
ldapIds,
@@ -2589,7 +2621,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
it('should succeed /abac/users/sync when exactly 100 usernames are provided (boundary)', async () => {
const usernames = Array.from({ length: 100 }, (_, i) => `boundary_user_${i}`);
await request
- .post(`${v1}/abac/users/sync`)
+ .post(api('abac/users/sync'))
.set(credentials)
.send({
usernames,
@@ -2667,7 +2699,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
expect(sergeiInitialAttrs[0].values).to.include(initialDept);
await request
- .post(`${v1}/abac/users/sync`)
+ .post(api('abac/users/sync'))
.set(credentials)
.send({
usernames: ['david.scott', 'sergei.krikalev'],
@@ -3424,6 +3456,118 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
});
});
+ describe('Re-evaluation via POST /abac/users/sync', () => {
+ describe('PDP DENY removes the synced user', () => {
+ let room: IRoom;
+ let user: IUser;
+ const username = `abac-sync-deny-${Date.now()}`;
+ const email = `${username}@rocket.chat`;
+
+ before(async function () {
+ this.timeout(15000);
+
+ user = await createUser({ username, email });
+ room = (await createRoom({ type: 'p', name: `extpdp-sync-deny-${Date.now()}` })).body.group;
+ await request
+ .post(api('groups.invite'))
+ .set(credentials)
+ .send({ roomId: room._id, usernames: [user.username] })
+ .expect(200);
+
+ await mockServerReset();
+ await seedDefaultMocks();
+ await seedBulkDecisionByEntity([adminEmail, email], 'DECISION_DENY');
+
+ await request
+ .post(api(`abac/rooms/${room._id}/attributes/${attrKey}`))
+ .set(credentials)
+ .send({ values: ['alpha'] })
+ .expect(200);
+ });
+
+ after(async () => {
+ await Promise.all([deleteRoom({ type: 'p', roomId: room._id }), deleteUser(user)]);
+ });
+
+ it('keeps the user before re-evaluation', async () => {
+ const res = await request.get(api('groups.members')).set(credentials).query({ roomId: room._id }).expect(200);
+ const usernames = res.body.members.map((m: IUser) => m.username);
+ expect(usernames).to.include(user.username);
+ });
+
+ it('removes the user when the Virtru PDP returns DENY', async () => {
+ await mockServerReset();
+ await seedDefaultMocks();
+ await seedBulkDecisionByEntity([adminEmail], 'DECISION_DENY');
+
+ await request
+ .post(api('abac/users/sync'))
+ .set(credentials)
+ .send({ usernames: [user.username] })
+ .expect(200);
+
+ const res = await request.get(api('groups.members')).set(credentials).query({ roomId: room._id }).expect(200);
+ const usernames = res.body.members.map((m: IUser) => m.username);
+ expect(usernames).to.not.include(user.username);
+ });
+
+ it('keeps the room creator (permitted) after re-evaluation', async () => {
+ const res = await request.get(api('groups.members')).set(credentials).query({ roomId: room._id }).expect(200);
+ const memberIds = res.body.members.map((m: IUser) => m._id);
+ expect(memberIds).to.include(credentials['X-User-Id']);
+ });
+ });
+
+ describe('PDP PERMIT keeps the synced user', () => {
+ let room: IRoom;
+ let user: IUser;
+ const username = `abac-sync-permit-${Date.now()}`;
+ const email = `${username}@rocket.chat`;
+
+ before(async function () {
+ this.timeout(15000);
+
+ user = await createUser({ username, email });
+ room = (await createRoom({ type: 'p', name: `extpdp-sync-permit-${Date.now()}` })).body.group;
+ await request
+ .post(api('groups.invite'))
+ .set(credentials)
+ .send({ roomId: room._id, usernames: [user.username] })
+ .expect(200);
+
+ await mockServerReset();
+ await seedDefaultMocks();
+ await seedBulkDecisionByEntity([adminEmail, email], 'DECISION_DENY');
+
+ await request
+ .post(api(`abac/rooms/${room._id}/attributes/${attrKey}`))
+ .set(credentials)
+ .send({ values: ['alpha'] })
+ .expect(200);
+ });
+
+ after(async () => {
+ await Promise.all([deleteRoom({ type: 'p', roomId: room._id }), deleteUser(user)]);
+ });
+
+ it('keeps the user when the Virtru PDP returns PERMIT', async () => {
+ await mockServerReset();
+ await seedDefaultMocks();
+ await seedBulkDecisionByEntity([adminEmail, email], 'DECISION_DENY');
+
+ await request
+ .post(api('abac/users/sync'))
+ .set(credentials)
+ .send({ usernames: [user.username] })
+ .expect(200);
+
+ const res = await request.get(api('groups.members')).set(credentials).query({ roomId: room._id }).expect(200);
+ const usernames = res.body.members.map((m: IUser) => m.username);
+ expect(usernames).to.include(user.username);
+ });
+ });
+ });
+
describe('[GET] /abac/pdp/health', () => {
beforeEach(async () => {
await mockServerReset();
@@ -3945,7 +4089,7 @@ const addAbacAttributesToUserDirectly = async (userId: string, abacAttributes: I
it('POST /abac/users/sync is NOT blocked by external attribute store (no error-abac-attribute-store-external)', async () => {
const res = await request
- .post(`${v1}/abac/users/sync`)
+ .post(api('abac/users/sync'))
.set(credentials)
.send({ usernames: ['no-such-user-vstore'] });
expect(res.body?.error).to.not.equal('error-abac-attribute-store-external');
diff --git a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveJumpToMessage.tests.ts b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveJumpToMessage.tests.ts
index 791404e4f1d9f..0ce7279e89b39 100644
--- a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveJumpToMessage.tests.ts
+++ b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveJumpToMessage.tests.ts
@@ -500,6 +500,17 @@ describe('Create attachments for message URLs', () => {
image_size: 68016,
type: 'file',
description: 'chained 3 - file',
+ descriptionMd: [
+ {
+ type: 'PARAGRAPH',
+ value: [
+ {
+ type: 'PLAIN_TEXT',
+ value: 'chained 3 - file',
+ },
+ ],
+ },
+ ],
},
],
},
diff --git a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts
index c744883ce9e3c..c511b071ac1a0 100644
--- a/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts
+++ b/apps/meteor/tests/unit/server/services/messages/hooks/BeforeSaveMarkdownParser.tests.ts
@@ -82,4 +82,29 @@ describe('Markdown parser', () => {
expect(message).to.have.property('md');
});
+
+ it('should parse markdown on the first attachment only', async () => {
+ const markdownParser = new BeforeSaveMarkdownParser(true);
+
+ const message = await markdownParser.parseMarkdown({
+ message: createMessage('hey', {
+ attachments: [
+ {
+ description: 'hey ho',
+ },
+ {
+ description: 'lets go',
+ },
+ ],
+ }),
+ config: {},
+ });
+
+ expect(message).to.have.property('md');
+
+ const [attachment1, attachment2] = message.attachments || [];
+
+ expect(attachment1).to.have.property('descriptionMd');
+ expect(attachment2).to.not.have.property('descriptionMd');
+ });
});
diff --git a/ee/packages/abac/src/index.ts b/ee/packages/abac/src/index.ts
index 1b73bb16b49a1..047da168c79c3 100644
--- a/ee/packages/abac/src/index.ts
+++ b/ee/packages/abac/src/index.ts
@@ -12,6 +12,7 @@ import type {
AbacAuditReason,
AbacAttributeStoreType,
AbacPdpType,
+ AbacUserIdentifiers,
} from '@rocket.chat/core-typings';
import { Rooms, AbacAttributes, Users, Subscriptions } from '@rocket.chat/models';
import { escapeRegExp } from '@rocket.chat/string-helpers';
@@ -956,6 +957,30 @@ export class AbacService extends ServiceClass implements IAbacService {
logger.error({ msg: 'Failed to evaluate room membership', err });
}
}
+
+ async reevaluateUsers(identifiers: AbacUserIdentifiers): Promise {
+ if (!this.pdp || !(await this.pdp.isAvailable())) {
+ return;
+ }
+
+ const users = await Users.findUsersByIdentifiers(identifiers, {
+ projection: { _id: 1, emails: 1, username: 1, __rooms: 1 },
+ }).toArray();
+
+ if (!users.length) {
+ return;
+ }
+
+ try {
+ const nonCompliant = await this.pdp.reevaluateUsers(users);
+ if (Array.isArray(nonCompliant) && nonCompliant.length) {
+ await Promise.all(nonCompliant.map(({ user, room }) => limit(() => this.removeUserFromRoom(room, user as IUser, 'api'))));
+ }
+ } catch (err) {
+ logger.error({ msg: 'Failed to reevaluate users', err });
+ throw err;
+ }
+ }
}
export { LocalPDP, VirtruPDP } from './pdp';
diff --git a/ee/packages/abac/src/pdp/LocalPDP.ts b/ee/packages/abac/src/pdp/LocalPDP.ts
index 2b1d3ed51174e..6d16d81e16997 100644
--- a/ee/packages/abac/src/pdp/LocalPDP.ts
+++ b/ee/packages/abac/src/pdp/LocalPDP.ts
@@ -1,9 +1,10 @@
+import { LDAPEnterprise } from '@rocket.chat/core-services';
import type { IAbacAttributeDefinition, IRoom, AtLeast, IUser } from '@rocket.chat/core-typings';
import { Rooms, Users } from '@rocket.chat/models';
import { OnlyCompliantCanBeAddedToRoomError } from '../errors';
import { buildCompliantConditions, buildNonCompliantConditions, buildRoomNonCompliantConditionsFromSubject } from '../helper';
-import type { IPolicyDecisionPoint } from './types';
+import type { IPolicyDecisionPoint, ReevaluationUser } from './types';
export class LocalPDP implements IPolicyDecisionPoint {
async isAvailable(): Promise {
@@ -81,6 +82,10 @@ export class LocalPDP implements IPolicyDecisionPoint {
throw new Error('evaluateUserRooms is not implemented for LocalPDP');
}
+ async reevaluateUsers(users: ReevaluationUser[]): Promise {
+ await LDAPEnterprise.syncUsersAbacAttributesByIds(users.map((user) => user._id));
+ }
+
async checkUsernamesMatchAttributes(usernames: string[], attributes: IAbacAttributeDefinition[], _object: IRoom): Promise {
const nonCompliantUsersFromList = await Users.find(
{
diff --git a/ee/packages/abac/src/pdp/VirtruPDP.spec.ts b/ee/packages/abac/src/pdp/VirtruPDP.spec.ts
index 3a4b15e722f1f..d869fa685f8ee 100644
--- a/ee/packages/abac/src/pdp/VirtruPDP.spec.ts
+++ b/ee/packages/abac/src/pdp/VirtruPDP.spec.ts
@@ -470,6 +470,38 @@ describe('VirtruPDP — PDP unreachable (decision call rejects)', () => {
});
});
+describe('reevaluateUsers', () => {
+ const room = { _id: 'r1', abacAttributes: [{ key: 'clearance', values: ['secret'] }] };
+
+ it('returns no pairs when users have no ABAC rooms', async () => {
+ const pdp = new VirtruPDP(mkClient());
+ const result = await pdp.reevaluateUsers([user({ _id: 'u1', __rooms: [] })]);
+ expect(result).toEqual([]);
+ expect(roomsFindPrivateRoomsByIdsWithAbacAttributes).not.toHaveBeenCalled();
+ });
+
+ it('returns non-compliant {user, room} pairs for denied users', async () => {
+ roomsFindPrivateRoomsByIdsWithAbacAttributes.mockReturnValue(asyncIterable([room]));
+ const apiCall = jest.fn().mockResolvedValue(denyFor(['r1']));
+ const pdp = new VirtruPDP(mkClient({ apiCall }));
+
+ const u = user({ _id: 'u1', __rooms: ['r1'] });
+ const result = await pdp.reevaluateUsers([u]);
+
+ expect(result).toEqual([{ user: u, room }]);
+ });
+
+ it('returns no pairs when the user is permitted', async () => {
+ roomsFindPrivateRoomsByIdsWithAbacAttributes.mockReturnValue(asyncIterable([room]));
+ const apiCall = jest.fn().mockResolvedValue(permitFor(['r1']));
+ const pdp = new VirtruPDP(mkClient({ apiCall }));
+
+ const result = await pdp.reevaluateUsers([user({ _id: 'u1', __rooms: ['r1'] })]);
+
+ expect(result).toEqual([]);
+ });
+});
+
describe('VirtruPDP.getHealthStatus', () => {
const platformOk = () => okJson({ status: 'SERVING' });
const authOk = () => okJson({});
diff --git a/ee/packages/abac/src/pdp/VirtruPDP.ts b/ee/packages/abac/src/pdp/VirtruPDP.ts
index 671e91642daf7..86d338e6640c0 100644
--- a/ee/packages/abac/src/pdp/VirtruPDP.ts
+++ b/ee/packages/abac/src/pdp/VirtruPDP.ts
@@ -1,11 +1,12 @@
import type { IAbacAttributeDefinition, IRoom, IUser, AtLeast } from '@rocket.chat/core-typings';
import { Rooms, Users } from '@rocket.chat/models';
import { serverFetch } from '@rocket.chat/server-fetch';
+import { isTruthy } from '@rocket.chat/tools';
import pLimit from 'p-limit';
import { OnlyCompliantCanBeAddedToRoomError, PdpHealthCheckError } from '../errors';
import { logger } from '../logger';
-import type { IPolicyDecisionPoint, IGetDecisionBulkRequest, IGetDecisionBulkResponse, IResourceDecision } from './types';
+import type { IPolicyDecisionPoint, IGetDecisionBulkRequest, IGetDecisionBulkResponse, IResourceDecision, ReevaluationUser } from './types';
import { HEALTH_CHECK_TIMEOUT } from '../clients/virtru/VirtruClient';
import type { VirtruClient } from '../clients/virtru/VirtruClient';
import { buildEntityIdentifier, buildAttributeFqns, getUserEntityKey } from '../clients/virtru/identity';
@@ -350,6 +351,31 @@ export class VirtruPDP implements IPolicyDecisionPoint {
return nonCompliant;
}
+ async reevaluateUsers(users: ReevaluationUser[]): Promise; room: IRoom }>> {
+ const roomIds = [...new Set(users.flatMap((u) => u.__rooms ?? []))];
+ if (!roomIds.length) {
+ return [];
+ }
+
+ const abacRoomCursor = Rooms.findPrivateRoomsByIdsWithAbacAttributes(roomIds, {
+ projection: { _id: 1, abacAttributes: 1 },
+ });
+
+ const abacRoomById = new Map();
+ for await (const room of abacRoomCursor) {
+ abacRoomById.set(room._id, room);
+ }
+
+ const entries = users
+ .map((user) => {
+ const rooms = (user.__rooms ?? []).map((rid) => abacRoomById.get(rid)).filter(isTruthy);
+ return rooms.length ? { user, rooms } : null;
+ })
+ .filter(isTruthy);
+
+ return this.evaluateUserRooms(entries);
+ }
+
async onSubjectAttributesChanged(user: IUser, _next: IAbacAttributeDefinition[]): Promise {
const roomIds = user.__rooms;
if (!roomIds?.length) {
diff --git a/ee/packages/abac/src/pdp/types.ts b/ee/packages/abac/src/pdp/types.ts
index 39073421d656c..a405104ec2d76 100644
--- a/ee/packages/abac/src/pdp/types.ts
+++ b/ee/packages/abac/src/pdp/types.ts
@@ -28,6 +28,8 @@ export interface IGetDecisionBulkResponse {
}>;
}
+export type ReevaluationUser = Pick;
+
export interface IPolicyDecisionPoint {
isAvailable(): Promise;
@@ -53,6 +55,8 @@ export interface IPolicyDecisionPoint {
rooms: AtLeast[];
}>,
): Promise; room: IRoom }>>;
+
+ reevaluateUsers(users: ReevaluationUser[]): Promise; room: IRoom }>>;
}
export interface IVirtruPDPConfig {
diff --git a/ee/packages/abac/src/service.spec.ts b/ee/packages/abac/src/service.spec.ts
index 49a5726b5ad35..f815171579011 100644
--- a/ee/packages/abac/src/service.spec.ts
+++ b/ee/packages/abac/src/service.spec.ts
@@ -59,6 +59,8 @@ const mockCreateAuditServerEvent = jest.fn();
const mockRoomsFindAllPrivateAbac = jest.fn();
const mockUsersFindActiveByRoomIds = jest.fn();
const mockRoomRemoveUserFromRoom = jest.fn();
+const mockUsersFindUsersByIdentifiers = jest.fn();
+const mockLdapSyncByIds = jest.fn();
jest.mock('@rocket.chat/models', () => ({
Rooms: {
@@ -90,6 +92,7 @@ jest.mock('@rocket.chat/models', () => ({
Users: {
find: (...args: any[]) => mockUsersFind(...args),
findActiveByRoomIds: (...args: any[]) => mockUsersFindActiveByRoomIds(...args),
+ findUsersByIdentifiers: (...args: any[]) => mockUsersFindUsersByIdentifiers(...args),
setAbacAttributesById: (...args: any[]) => mockUsersSetAbacAttributesById(...args),
unsetAbacAttributesById: (...args: any[]) => mockUsersUnsetAbacAttributesById(...args),
findOneAndUpdate: (...args: any[]) => mockUsersUpdateOne(...args),
@@ -116,6 +119,9 @@ jest.mock('@rocket.chat/core-services', () => {
Room: {
removeUserFromRoom: (...args: any[]) => mockRoomRemoveUserFromRoom(...args),
},
+ LDAPEnterprise: {
+ syncUsersAbacAttributesByIds: (...args: any[]) => mockLdapSyncByIds(...args),
+ },
api: {
broadcast: jest.fn(),
},
@@ -1983,4 +1989,46 @@ describe('AbacService (unit)', () => {
expect(pdpStrategySpy).toHaveBeenCalledWith('local');
});
});
+
+ describe('reevaluateUsers', () => {
+ const usersCursor = (items: any[]) => ({ toArray: () => Promise.resolve(items) });
+
+ it('local PDP: forwards resolved user ids to the LDAP broker and removes nothing', async () => {
+ service.setPdpStrategy('local');
+ mockUsersFindUsersByIdentifiers.mockReturnValue(usersCursor([{ _id: 'u1' }, { _id: 'u2' }]));
+
+ await service.reevaluateUsers({ usernames: ['bob'] });
+
+ expect(mockUsersFindUsersByIdentifiers).toHaveBeenCalledWith(
+ { usernames: ['bob'] },
+ { projection: { _id: 1, emails: 1, username: 1, __rooms: 1 } },
+ );
+ expect(mockLdapSyncByIds).toHaveBeenCalledWith(['u1', 'u2']);
+ expect(mockRoomRemoveUserFromRoom).not.toHaveBeenCalled();
+ });
+
+ it('virtru PDP: removes the non-compliant pairs the PDP returns', async () => {
+ service.setPdpStrategy('virtru');
+ const u1 = { _id: 'u1', emails: [{ address: 'u1@x.com' }], username: 'u1' };
+ const room = { _id: 'r1', abacAttributes: [] };
+ mockUsersFindUsersByIdentifiers.mockReturnValue(usersCursor([u1]));
+ mockRoomRemoveUserFromRoom.mockResolvedValue(undefined);
+ jest.spyOn((service as any).pdp, 'isAvailable').mockResolvedValue(true);
+ jest.spyOn((service as any).pdp, 'reevaluateUsers').mockResolvedValue([{ user: u1, room }]);
+
+ await service.reevaluateUsers({ ids: ['u1'] });
+
+ expect(mockRoomRemoveUserFromRoom).toHaveBeenCalledTimes(1);
+ });
+
+ it('no-ops when no users match', async () => {
+ service.setPdpStrategy('local');
+ mockUsersFindUsersByIdentifiers.mockReturnValue(usersCursor([]));
+
+ await service.reevaluateUsers({ ids: ['missing'] });
+
+ expect(mockLdapSyncByIds).not.toHaveBeenCalled();
+ expect(mockRoomRemoveUserFromRoom).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts b/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts
index 67b9aa5ab16d9..4755688c9f006 100644
--- a/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts
+++ b/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts
@@ -312,7 +312,9 @@ export class OmnichannelTranscript extends ServiceClass implements IOmnichannelT
}
}
- const msg = message.msg || '';
+ // When you send a file message, the things you type in the modal are not "msg", they're in "description" of the attachment
+ // So, we'll fetch the the msg, if empty, go for the first description on an attachment, if empty, empty string
+ const msg = message.msg || message.attachments.find((attachment) => attachment.description)?.description || '';
// Remove nulls from final array
messagesData.push({
msg,
diff --git a/eslint.config.mjs b/eslint.config.mjs
index f17879fb337ea..d675e671053ba 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -432,14 +432,6 @@ export default [
],
},
},
- // FIXME: React 19 useEffectEvent conflicts with fuselage-hooks
- {
- files: ['**/*.@(ts|tsx)'],
- rules: {
- 'react-hooks/exhaustive-deps': 'warn',
- 'react-hooks/rules-of-hooks': 'warn',
- },
- },
// FIXME: these rules require type information and the files are not included in the main tsconfig.json
{
files: [
diff --git a/packages/core-services/src/types/IAbacService.ts b/packages/core-services/src/types/IAbacService.ts
index 4d558d50d85d1..3eac9e59b8351 100644
--- a/packages/core-services/src/types/IAbacService.ts
+++ b/packages/core-services/src/types/IAbacService.ts
@@ -7,6 +7,7 @@ import type {
AbacAccessOperation,
AbacObjectType,
ILDAPEntry,
+ AbacUserIdentifiers,
} from '@rocket.chat/core-typings';
export type AbacActor = Pick;
@@ -49,6 +50,7 @@ export interface IAbacService {
): Promise;
addSubjectAttributes(user: IUser, ldapUser: ILDAPEntry, map: Record, actor: AbacActor | undefined): Promise;
evaluateRoomMembership(): Promise;
+ reevaluateUsers(identifiers: AbacUserIdentifiers): Promise;
getPDPHealth(): Promise;
isExternalAttributeStore(): Promise;
}
diff --git a/packages/core-services/src/types/ILDAPEEService.ts b/packages/core-services/src/types/ILDAPEEService.ts
index 0879ea5c266b1..b8ef92d8c8a04 100644
--- a/packages/core-services/src/types/ILDAPEEService.ts
+++ b/packages/core-services/src/types/ILDAPEEService.ts
@@ -8,4 +8,5 @@ export interface ILDAPEEService {
syncLogout(): Promise;
syncAbacAttributes(): Promise;
syncUsersAbacAttributes(users: FindCursor): Promise;
+ syncUsersAbacAttributesByIds(userIds: string[]): Promise;
}
diff --git a/packages/core-typings/src/Abac.ts b/packages/core-typings/src/Abac.ts
index 80bff941e9adb..6cc78712e4b1e 100644
--- a/packages/core-typings/src/Abac.ts
+++ b/packages/core-typings/src/Abac.ts
@@ -13,3 +13,10 @@ export enum AbacObjectType {
export const isAbacPdpType = (value: unknown): value is AbacPdpType => value === 'local' || value === 'virtru';
export const isAbacAttributeStoreType = (value: unknown): value is AbacAttributeStoreType => value === 'local' || value === 'virtru';
+
+export type AbacUserIdentifiers = {
+ usernames?: string[];
+ ids?: string[];
+ emails?: string[];
+ ldapIds?: string[];
+};
diff --git a/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentBase.ts b/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentBase.ts
index 7d236bcafa2be..82ce0db3325ef 100644
--- a/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentBase.ts
+++ b/packages/core-typings/src/IMessage/MessageAttachment/MessageAttachmentBase.ts
@@ -7,6 +7,7 @@ export type MessageAttachmentBase = {
collapsed?: boolean;
/** description isn't being used on client for non-image attachments, we're keeping it for backward compatibility */
description?: string;
+ descriptionMd?: Root;
text?: string;
md?: Root;
size?: number;
diff --git a/packages/fuselage-ui-kit/src/hooks/useUiKitState.ts b/packages/fuselage-ui-kit/src/hooks/useUiKitState.ts
index d993ea6616ee0..badf6e46bf309 100644
--- a/packages/fuselage-ui-kit/src/hooks/useUiKitState.ts
+++ b/packages/fuselage-ui-kit/src/hooks/useUiKitState.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent, useSafely } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, useSafely } from '@rocket.chat/fuselage-hooks';
import * as UiKit from '@rocket.chat/ui-kit';
import { useContext, useMemo, useState } from 'react';
@@ -46,7 +46,7 @@ export const useUiKitState = (
const [value, setValue] = useSafely(useState(_value));
const [loading, setLoading] = useSafely(useState(false));
- const actionFunction = useEffectEvent(async (e: any) => {
+ const actionFunction = useStableCallback(async (e: any) => {
// FIXME: fix typings
const {
target: { value: elValue },
@@ -86,7 +86,7 @@ export const useUiKitState = (
// Used for triggering actions on text inputs. Removing the load state
// makes the text input field remain focused after running the action
- const noLoadStateActionFunction = useEffectEvent(async (e: any) => {
+ const noLoadStateActionFunction = useStableCallback(async (e: any) => {
// FIXME: fix typings
const {
target: { value },
@@ -108,7 +108,7 @@ export const useUiKitState = (
);
});
- const stateFunction = useEffectEvent(async (e: any) => {
+ const stateFunction = useStableCallback(async (e: any) => {
// FIXME: fix typings
const {
target: { value },
diff --git a/packages/ui-avatar/src/components/BaseAvatar.tsx b/packages/ui-avatar/src/components/BaseAvatar.tsx
index 72845a42ce5ae..49e263cf7cf37 100644
--- a/packages/ui-avatar/src/components/BaseAvatar.tsx
+++ b/packages/ui-avatar/src/components/BaseAvatar.tsx
@@ -1,6 +1,6 @@
import type { AvatarProps } from '@rocket.chat/fuselage';
import { Avatar, Skeleton } from '@rocket.chat/fuselage';
-import { useEffectEvent, usePrevious } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback, usePrevious } from '@rocket.chat/fuselage-hooks';
import type { SyntheticEvent } from 'react';
import { useState } from 'react';
@@ -10,12 +10,12 @@ const BaseAvatar = ({ url, onLoad, onError, size, ...props }: BaseAvatarProps) =
const [unloaded, setUnloaded] = useState(false);
const prevUrl = usePrevious(url);
- const handleLoad = useEffectEvent((event: SyntheticEvent) => {
+ const handleLoad = useStableCallback((event: SyntheticEvent) => {
setUnloaded(false);
onLoad?.(event);
});
- const handleError = useEffectEvent((event: SyntheticEvent) => {
+ const handleError = useStableCallback((event: SyntheticEvent) => {
setUnloaded(true);
onError?.(event);
});
diff --git a/packages/ui-client/src/components/GenericMenu/hooks/useHandleMenuAction.tsx b/packages/ui-client/src/components/GenericMenu/hooks/useHandleMenuAction.tsx
index 3edc2c67499fb..0a08f39216528 100644
--- a/packages/ui-client/src/components/GenericMenu/hooks/useHandleMenuAction.tsx
+++ b/packages/ui-client/src/components/GenericMenu/hooks/useHandleMenuAction.tsx
@@ -1,10 +1,10 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { Key } from 'react';
import type { GenericMenuItemProps } from '../GenericMenuItem';
export const useHandleMenuAction = (items: GenericMenuItemProps[], callbackAction?: () => void) =>
- useEffectEvent((id: Key) => {
+ useStableCallback((id: Key) => {
const item = items.find((item) => item.id === id && !!item.onClick);
if (item) {
item.onClick?.();
diff --git a/packages/ui-client/src/components/Modal/GenericModal/GenericModal.tsx b/packages/ui-client/src/components/Modal/GenericModal/GenericModal.tsx
index 19ac8d62bf7e0..e14c655566e76 100644
--- a/packages/ui-client/src/components/Modal/GenericModal/GenericModal.tsx
+++ b/packages/ui-client/src/components/Modal/GenericModal/GenericModal.tsx
@@ -12,7 +12,7 @@ import {
ModalTagline,
ModalTitle,
} from '@rocket.chat/fuselage';
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type { Keys as IconName } from '@rocket.chat/icons';
import type { ReactElement, ReactNode, ComponentPropsWithoutRef } from 'react';
import { useId, useEffect, useRef } from 'react';
@@ -103,22 +103,22 @@ const GenericModal = ({
const taglineColor = variant === 'upsell' ? 'annotation' : undefined;
- const handleConfirm = useEffectEvent(() => {
+ const handleConfirm = useStableCallback(() => {
dismissedRef.current = false;
void onConfirm?.();
});
- const handleCancel = useEffectEvent(() => {
+ const handleCancel = useStableCallback(() => {
dismissedRef.current = false;
void onCancel?.();
});
- const handleCloseButtonClick = useEffectEvent(() => {
+ const handleCloseButtonClick = useStableCallback(() => {
dismissedRef.current = true;
void onClose?.();
});
- const handleDismiss = useEffectEvent(() => {
+ const handleDismiss = useStableCallback(() => {
dismissedRef.current = true;
void onDismiss?.();
});
diff --git a/packages/ui-client/src/components/Modal/ModalRegion.tsx b/packages/ui-client/src/components/Modal/ModalRegion.tsx
index 1619e57cfaee7..42982dee83c12 100644
--- a/packages/ui-client/src/components/Modal/ModalRegion.tsx
+++ b/packages/ui-client/src/components/Modal/ModalRegion.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useCurrentModal, useModal } from '@rocket.chat/ui-contexts';
import { lazy, Suspense } from 'react';
@@ -10,7 +10,7 @@ const FocusScope = lazy(() => import('@react-aria/focus').then((module) => ({ de
const ModalRegion = () => {
const currentModal = useCurrentModal();
const { setModal } = useModal();
- const handleDismiss = useEffectEvent(() => {
+ const handleDismiss = useStableCallback(() => {
setModal(null);
});
diff --git a/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomList.tsx b/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomList.tsx
index 57448c8ef5516..0b64b4477c261 100644
--- a/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomList.tsx
+++ b/packages/ui-client/src/components/MultiSelectCustom/MultiSelectCustomList.tsx
@@ -1,6 +1,6 @@
import { Box, CheckBox, Icon, Option, OptionIcon, SearchInput, Tile } from '@rocket.chat/fuselage';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
-import type { FormEvent } from 'react';
+import type { ChangeEvent } from 'react';
import { Fragment, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -25,14 +25,14 @@ const MultiSelectCustomList = ({
searchBarText,
}: {
options: OptionProp[];
- onSelected: (item: OptionProp, e?: FormEvent) => void;
+ onSelected: (item: OptionProp, e?: ChangeEvent) => void;
searchBarText?: string;
}) => {
const { t } = useTranslation();
const [text, setText] = useState('');
- const handleChange = useCallback((event: FormEvent) => setText(event.currentTarget.value), []);
+ const handleChange = useCallback((event: ChangeEvent) => setText(event.currentTarget.value), []);
const filteredOptions = useFilteredOptions(text, options);
diff --git a/packages/ui-client/src/components/SidebarToggler/SidebarToggler.tsx b/packages/ui-client/src/components/SidebarToggler/SidebarToggler.tsx
index 52e455a38f015..791599afe866e 100644
--- a/packages/ui-client/src/components/SidebarToggler/SidebarToggler.tsx
+++ b/packages/ui-client/src/components/SidebarToggler/SidebarToggler.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useLayout, useSession } from '@rocket.chat/ui-contexts';
import { memo } from 'react';
@@ -10,7 +10,7 @@ const SideBarToggler = () => {
const isLayoutEmbedded = useEmbeddedLayout();
const unreadMessagesBadge = useSession('unread') as number | string | undefined;
- const toggleSidebar = useEffectEvent(() => sidebar.toggle());
+ const toggleSidebar = useStableCallback(() => sidebar.toggle());
return ;
};
diff --git a/packages/ui-client/src/components/Wizard/useWizard.tsx b/packages/ui-client/src/components/Wizard/useWizard.tsx
index c536ad598c49e..c64a09a1e3c1d 100644
--- a/packages/ui-client/src/components/Wizard/useWizard.tsx
+++ b/packages/ui-client/src/components/Wizard/useWizard.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useMemo, useState } from 'react';
import type { WizardAPI } from './WizardContext';
@@ -25,7 +25,7 @@ export const useWizard = ({ steps: stepsMetadata }: UseWizardProps) => {
* Registers a new step in the wizard.
* If a step with the same ID already exists, it updates the existing step.
*/
- const register = useEffectEvent((stepMetadata: StepMetadata) => {
+ const register = useStableCallback((stepMetadata: StepMetadata) => {
const step = steps.append(stepMetadata);
return () => steps.remove(step.id);
});
@@ -36,7 +36,7 @@ export const useWizard = ({ steps: stepsMetadata }: UseWizardProps) => {
*
* @param {StepNode} step - The step to navigate to.
*/
- const goTo = useEffectEvent(async (step: StepNode) => {
+ const goTo = useStableCallback(async (step: StepNode) => {
if (step.disabled) {
return;
}
@@ -48,7 +48,7 @@ export const useWizard = ({ steps: stepsMetadata }: UseWizardProps) => {
* Navigates to the next step in the wizard.
* If there is no next step, it does nothing.
*/
- const next = useEffectEvent(async () => {
+ const next = useStableCallback(async () => {
if (!currentStep?.next) {
return;
}
@@ -61,7 +61,7 @@ export const useWizard = ({ steps: stepsMetadata }: UseWizardProps) => {
* Navigates to the previous step in the wizard.
* If there is no previous step, it does nothing.
*/
- const previous = useEffectEvent(async () => {
+ const previous = useStableCallback(async () => {
if (!currentStep?.prev) {
return;
}
@@ -74,7 +74,7 @@ export const useWizard = ({ steps: stepsMetadata }: UseWizardProps) => {
* Resets the next steps in the wizard.
* It disables all steps that come after the current step.
*/
- const resetNextSteps = useEffectEvent(() => {
+ const resetNextSteps = useStableCallback(() => {
if (!currentStep) {
return;
}
diff --git a/packages/ui-client/src/hooks/useGoToDirectMessage.ts b/packages/ui-client/src/hooks/useGoToDirectMessage.ts
index 2a2e76cb2f4d8..e4c5ddc8cbd77 100644
--- a/packages/ui-client/src/hooks/useGoToDirectMessage.ts
+++ b/packages/ui-client/src/hooks/useGoToDirectMessage.ts
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { usePermission, useUserSubscriptionByName, useRouter } from '@rocket.chat/ui-contexts';
// TODO: Routes type definitions are declared in-file for most places, so this route doesn't exist in this package
@@ -26,7 +26,7 @@ export const useGoToDirectMessage = (targetUser: { username?: string }, openRoom
const alreadyOpen = openRoomId && usernameSubscription?.rid === openRoomId;
const shouldOpen = targetUser.username && hasPermissionOrSubscription && !alreadyOpen;
- const openDirectMessage = useEffectEvent(
+ const openDirectMessage = useStableCallback(
() =>
targetUser.username &&
router.navigate({
diff --git a/packages/ui-client/src/providers/ModalProvider/ModalProvider.tsx b/packages/ui-client/src/providers/ModalProvider/ModalProvider.tsx
index 4fed55cec20fd..ebb28f59f3fb6 100644
--- a/packages/ui-client/src/providers/ModalProvider/ModalProvider.tsx
+++ b/packages/ui-client/src/providers/ModalProvider/ModalProvider.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { ModalContext } from '@rocket.chat/ui-contexts';
import type { ReactNode } from 'react';
import { useMemo, memo, useSyncExternalStore } from 'react';
@@ -13,7 +13,7 @@ type ModalProviderProps = {
const ModalProvider = ({ children, region }: ModalProviderProps) => {
const currentModal = useSyncExternalStore(modalStore.subscribe, modalStore.getSnapshot);
- const setModal = useEffectEvent((modal: ReactNode | (() => ReactNode)) => {
+ const setModal = useStableCallback((modal: ReactNode | (() => ReactNode)) => {
if (typeof modal === 'function') {
modalStore.open(modal(), region);
return;
diff --git a/packages/ui-client/src/views/setupWizard/providers/SetupWizardProvider.tsx b/packages/ui-client/src/views/setupWizard/providers/SetupWizardProvider.tsx
index 682ed031d775c..50f4c1a83503a 100644
--- a/packages/ui-client/src/views/setupWizard/providers/SetupWizardProvider.tsx
+++ b/packages/ui-client/src/views/setupWizard/providers/SetupWizardProvider.tsx
@@ -1,4 +1,4 @@
-import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { validateEmail } from '@rocket.chat/tools';
import {
useToastMessageDispatch,
@@ -167,7 +167,7 @@ const SetupWizardProvider = ({ children }: SetupWizardProviderProps) => {
const queryClient = useQueryClient();
- const registerServer: HandleRegisterServer = useEffectEvent(
+ const registerServer: HandleRegisterServer = useStableCallback(
async ({ email, resend = false }: { email: string; resend?: boolean }): Promise => {
try {
const { intentData } = await createRegistrationIntent({ resend, email });
@@ -187,7 +187,7 @@ const SetupWizardProvider = ({ children }: SetupWizardProviderProps) => {
},
);
- const completeSetupWizard = useEffectEvent(async (): Promise => {
+ const completeSetupWizard = useStableCallback(async (): Promise => {
dispatchToastMessage({ type: 'success', message: t('Your_workspace_is_ready') });
return setShowSetupWizard('completed');
});
diff --git a/packages/ui-voip/src/providers/useMediaSession.ts b/packages/ui-voip/src/providers/useMediaSession.ts
index 893bbd06ca5d0..3798c874b8193 100644
--- a/packages/ui-voip/src/providers/useMediaSession.ts
+++ b/packages/ui-voip/src/providers/useMediaSession.ts
@@ -107,7 +107,7 @@ export type MediaSessionStateWithWidgetControls = {
};
export const useMediaSession = (instance?: MediaSignalingSession): MediaSessionStateWithWidgetControls => {
- const [mediaSession, dispatch] = useReducer(reducer, defaultSessionInfo);
+ const [mediaSession, dispatch] = useReducer(reducer, defaultSessionInfo);
const getAvatarUrl = useUserAvatarPath();
diff --git a/packages/ui-voip/src/views/CallHistoryContextualbar/CallHistoryActions.stories.tsx b/packages/ui-voip/src/views/CallHistoryContextualbar/CallHistoryActions.stories.tsx
index a42ec739e3465..d49ef679fbe88 100644
--- a/packages/ui-voip/src/views/CallHistoryContextualbar/CallHistoryActions.stories.tsx
+++ b/packages/ui-voip/src/views/CallHistoryContextualbar/CallHistoryActions.stories.tsx
@@ -1,5 +1,5 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
-import type { Meta, StoryFn, StoryObj } from '@storybook/react';
+import type { Decorator, Meta, StoryObj } from '@storybook/react';
import type { HistoryActionCallbacks } from './CallHistoryActions';
import CallHistoryActions from './CallHistoryActions';
@@ -37,8 +37,8 @@ const getArgs = (index: number) => {
return Object.fromEntries(actionList.slice(0, index).map((action) => [action, noop])) as HistoryActionCallbacks;
};
-const getDecorator = (state: State) => {
- return (Story: StoryFn) => (
+const getDecorator = (state: State): Decorator => {
+ return (Story) => (