From 63ade36c54fed6f07cad04a0d5b3d737086a776c Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Wed, 9 Jul 2025 11:48:43 +0530 Subject: [PATCH 1/2] fix: save title input on closing --- .../core/components/issues/title-input.tsx | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/web/core/components/issues/title-input.tsx b/apps/web/core/components/issues/title-input.tsx index bc04dbbb4d6..46b3a599b27 100644 --- a/apps/web/core/components/issues/title-input.tsx +++ b/apps/web/core/components/issues/title-input.tsx @@ -1,6 +1,6 @@ "use client"; -import { FC, useState, useEffect, useCallback } from "react"; +import { FC, useState, useEffect, useCallback, useRef } from "react"; import { observer } from "mobx-react"; import { useTranslation } from "@plane/i18n"; import { TNameDescriptionLoader } from "@plane/types"; @@ -42,19 +42,34 @@ export const IssueTitleInput: FC = observer((props) => { // states const [title, setTitle] = useState(""); const [isLengthVisible, setIsLengthVisible] = useState(false); + // ref to track if there are unsaved changes + const hasUnsavedChanges = useRef(false); + // ref to store current title value for cleanup function + const currentTitleRef = useRef(title); // hooks const debouncedValue = useDebounce(title, 1500); useEffect(() => { - if (value) setTitle(value); + if (value) { + setTitle(value); + currentTitleRef.current = value; + // Reset unsaved changes flag when value is set from props + hasUnsavedChanges.current = false; + } }, [value]); + // Keep currentTitleRef in sync with title state + useEffect(() => { + currentTitleRef.current = title; + }, [title]); + useEffect(() => { const textarea = document.querySelector("#title-input"); if (debouncedValue && debouncedValue !== value) { if (debouncedValue.trim().length > 0) { issueOperations.update(workspaceSlug, projectId, issueId, { name: debouncedValue }).finally(() => { setIsSubmitting("saved"); + hasUnsavedChanges.current = false; if (textarea && !textarea.matches(":focus")) { const trimmedTitle = debouncedValue.trim(); if (trimmedTitle !== title) setTitle(trimmedTitle); @@ -63,6 +78,7 @@ export const IssueTitleInput: FC = observer((props) => { } else { setTitle(value || ""); setIsSubmitting("saved"); + hasUnsavedChanges.current = false; } } // DO NOT Add more dependencies here. It will cause multiple requests to be sent. @@ -76,9 +92,11 @@ export const IssueTitleInput: FC = observer((props) => { if (trimmedTitle.length > 0) { setTitle(trimmedTitle); setIsSubmitting("submitting"); + hasUnsavedChanges.current = true; } else { setTitle(value || ""); setIsSubmitting("saved"); + hasUnsavedChanges.current = false; } } }; @@ -95,10 +113,30 @@ export const IssueTitleInput: FC = observer((props) => { }; }, [title, isSubmitting, setIsSubmitting]); + // Save on unmount if there are unsaved changes + useEffect( + () => () => { + if (hasUnsavedChanges.current && currentTitleRef.current.trim().length > 0) { + issueOperations + .update(workspaceSlug, projectId, issueId, { name: currentTitleRef.current.trim() }) + .catch((error) => { + console.error("Failed to save title on unmount:", error); + }) + .finally(() => { + setIsSubmitting("saved"); + hasUnsavedChanges.current = false; + }); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + const handleTitleChange = useCallback( (e: React.ChangeEvent) => { setIsSubmitting("submitting"); setTitle(e.target.value); + hasUnsavedChanges.current = true; }, [setIsSubmitting] ); From 70d541667fbe7aa82ff1719e2fe1896a810d0095 Mon Sep 17 00:00:00 2001 From: Palanikannan M Date: Wed, 9 Jul 2025 13:09:16 +0530 Subject: [PATCH 2/2] fix: title input updatioin --- apps/web/core/components/issues/title-input.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/web/core/components/issues/title-input.tsx b/apps/web/core/components/issues/title-input.tsx index 46b3a599b27..e2b4be88142 100644 --- a/apps/web/core/components/issues/title-input.tsx +++ b/apps/web/core/components/issues/title-input.tsx @@ -58,11 +58,6 @@ export const IssueTitleInput: FC = observer((props) => { } }, [value]); - // Keep currentTitleRef in sync with title state - useEffect(() => { - currentTitleRef.current = title; - }, [title]); - useEffect(() => { const textarea = document.querySelector("#title-input"); if (debouncedValue && debouncedValue !== value) { @@ -135,7 +130,9 @@ export const IssueTitleInput: FC = observer((props) => { const handleTitleChange = useCallback( (e: React.ChangeEvent) => { setIsSubmitting("submitting"); - setTitle(e.target.value); + const titleFromEvent = e.target.value; + setTitle(titleFromEvent); + currentTitleRef.current = titleFromEvent; hasUnsavedChanges.current = true; }, [setIsSubmitting]