From 02ec642be61d9796e7b22be9fff33860c89ca824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Mon, 16 Mar 2026 16:14:47 +0100 Subject: [PATCH] fix: avoid stale closure in keyboard submit handler --- src/components/ChatViewer.tsx | 9 +++++++- src/components/editor/MentionEditor.tsx | 30 ++++++++++++++----------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index 3a1eecc..643e807 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -671,6 +671,8 @@ export function ChatViewer({ // Track reply context (which message is being replied to) const [replyTo, setReplyTo] = useState(); + const replyToRef = useRef(undefined); + replyToRef.current = replyTo; // State for loading older messages const [isLoadingOlder, setIsLoadingOlder] = useState(false); @@ -1210,7 +1212,12 @@ export function ChatViewer({ }} onSubmit={(content, emojiTags, blobAttachments) => { if (content.trim()) { - handleSend(content, replyTo, emojiTags, blobAttachments); + handleSend( + content, + replyToRef.current, + emojiTags, + blobAttachments, + ); } }} className="flex-1 min-w-0" diff --git a/src/components/editor/MentionEditor.tsx b/src/components/editor/MentionEditor.tsx index df29201..2f56936 100644 --- a/src/components/editor/MentionEditor.tsx +++ b/src/components/editor/MentionEditor.tsx @@ -7,6 +7,7 @@ import { useRef, } from "react"; import { useEditor, EditorContent } from "@tiptap/react"; +import type { Editor } from "@tiptap/core"; import Mention from "@tiptap/extension-mention"; import StarterKit from "@tiptap/starter-kit"; import Placeholder from "@tiptap/extension-placeholder"; @@ -77,22 +78,25 @@ export const MentionEditor = forwardRef< }, ref, ) => { - const handleSubmitRef = useRef<(editor: any) => void>(() => {}); + // Use a ref for onSubmit to avoid stale closures in TipTap keyboard handlers. + // The Enter key handler reads this ref at invocation time, ensuring it always + // has the latest callback (including any captured reply context). + const onSubmitRef = useRef(onSubmit); + onSubmitRef.current = onSubmit; - const handleSubmit = useCallback( - (editorInstance: any) => { - if (!editorInstance || !onSubmit) return; + const handleSubmit = useCallback((editorInstance: Editor) => { + const cb = onSubmitRef.current; + if (!cb) return; - const { text, emojiTags, blobAttachments } = - serializeInlineContent(editorInstance); - if (text) { - onSubmit(text, emojiTags, blobAttachments); - editorInstance.commands.clearContent(); - } - }, - [onSubmit], - ); + const { text, emojiTags, blobAttachments } = + serializeInlineContent(editorInstance); + if (text) { + cb(text, emojiTags, blobAttachments); + editorInstance.commands.clearContent(); + } + }, []); + const handleSubmitRef = useRef(handleSubmit); handleSubmitRef.current = handleSubmit; // React-based suggestion renderers (replace tippy.js + ReactRenderer)