[WIKI-528] fix: save the issue description on component's unmount as well #3598

This commit is contained in:
M. Palanikannan
2025-07-08 16:14:57 +05:30
committed by GitHub
parent 61c65cc894
commit db143fc166
3 changed files with 93 additions and 7 deletions

View File

@@ -1,11 +1,11 @@
"use client";
import { FC, useCallback, useEffect, useState } from "react";
import { FC, useCallback, useEffect, useState, useRef } from "react";
import debounce from "lodash/debounce";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
// plane imports
import type { EditorReadOnlyRefApi, EditorRefApi, TExtensions } from "@plane/editor";
import type { EditorRefApi, TExtensions } from "@plane/editor";
import { useTranslation } from "@plane/i18n";
import { EFileAssetType, TNameDescriptionLoader } from "@plane/types";
import { Loader } from "@plane/ui";
@@ -61,6 +61,8 @@ export const DescriptionInput: FC<DescriptionInputProps> = observer((props) => {
id: itemId,
description_html: initialValue,
});
// ref to track if there are unsaved changes
const hasUnsavedChanges = useRef(false);
// store hooks
const { getWorkspaceBySlug } = useWorkspace();
const { uploadEditorAsset } = useEditorAsset();
@@ -93,6 +95,8 @@ export const DescriptionInput: FC<DescriptionInputProps> = observer((props) => {
id: itemId,
description_html: initialValue === "" ? "<p></p>" : initialValue,
});
// Reset unsaved changes flag when form is reset
hasUnsavedChanges.current = false;
}, [initialValue, itemId, reset]);
// ADDING handleDescriptionFormSubmit TO DEPENDENCY ARRAY PRODUCES ADVERSE EFFECTS
@@ -100,11 +104,35 @@ export const DescriptionInput: FC<DescriptionInputProps> = observer((props) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedFormSave = useCallback(
debounce(async () => {
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
handleSubmit(handleDescriptionFormSubmit)().finally(() => {
setIsSubmitting("submitted");
hasUnsavedChanges.current = false;
});
}, 1500),
[handleSubmit, itemId]
);
// Save on unmount if there are unsaved changes
useEffect(
() => () => {
debouncedFormSave.cancel();
if (hasUnsavedChanges.current) {
handleSubmit(handleDescriptionFormSubmit)()
.catch((error) => {
console.error("Failed to save description on unmount:", error);
})
.finally(() => {
setIsSubmitting("submitted");
hasUnsavedChanges.current = false;
});
}
},
// since we don't want to save on unmount if there are no unsaved changes, no deps are needed
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return (
<>
{localDescription.description_html ? (
@@ -126,6 +154,7 @@ export const DescriptionInput: FC<DescriptionInputProps> = observer((props) => {
onChange={(_description: object, description_html: string) => {
setIsSubmitting("submitting");
onChange(description_html);
hasUnsavedChanges.current = true;
debouncedFormSave();
}}
placeholder={

View File

@@ -1,6 +1,6 @@
"use client";
import { FC, useCallback, useEffect } from "react";
import { FC, useCallback, useEffect, useRef } from "react";
import debounce from "lodash/debounce";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
@@ -44,6 +44,8 @@ export const ProjectDescriptionInput: FC<ProjectDescriptionInputProps> = observe
const { uploadEditorAsset } = useEditorAsset();
// plane hooks
const { t } = useTranslation();
// ref to track if there are unsaved changes
const hasUnsavedChanges = useRef(false);
const { handleSubmit, reset, control } = useForm<TProject>({
defaultValues: {
@@ -71,6 +73,8 @@ export const ProjectDescriptionInput: FC<ProjectDescriptionInputProps> = observe
id: project.id,
description_html: initialValue === "" ? "<p></p>" : initialValue,
});
// Reset unsaved changes flag when form is reset
hasUnsavedChanges.current = false;
}, [initialValue, reset]);
// ADDING handleDescriptionFormSubmit TO DEPENDENCY ARRAY PRODUCES ADVERSE EFFECTS
@@ -78,11 +82,35 @@ export const ProjectDescriptionInput: FC<ProjectDescriptionInputProps> = observe
// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedFormSave = useCallback(
debounce(async () => {
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
handleSubmit(handleDescriptionFormSubmit)().finally(() => {
setIsSubmitting("submitted");
hasUnsavedChanges.current = false;
});
}, 1500),
[handleSubmit, project.id]
);
// Save on unmount if there are unsaved changes
useEffect(
() => () => {
debouncedFormSave.cancel();
if (hasUnsavedChanges.current) {
handleSubmit(handleDescriptionFormSubmit)()
.catch((error) => {
console.error("Failed to save description on unmount:", error);
})
.finally(() => {
setIsSubmitting("submitted");
hasUnsavedChanges.current = false;
});
}
},
// since we don't want to save on unmount if there are no unsaved changes, no deps are needed
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return (
<>
{project?.description_html === undefined ? (
@@ -129,6 +157,7 @@ export const ProjectDescriptionInput: FC<ProjectDescriptionInputProps> = observe
onChange={(_description: object, description_html: string) => {
setIsSubmitting("submitting");
onChange(description_html);
hasUnsavedChanges.current = true;
debouncedFormSave();
}}
placeholder={

View File

@@ -1,6 +1,6 @@
"use client";
import { FC, useCallback, useEffect, useState } from "react";
import { FC, useCallback, useEffect, useRef, useState } from "react";
import debounce from "lodash/debounce";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
@@ -50,6 +50,8 @@ export const TeamspaceDescriptionInput: FC<TeamspaceDescriptionInputProps> = obs
id: teamspaceId,
description_html: initialValue,
});
// ref to track if there are unsaved changes
const hasUnsavedChanges = useRef(false);
const setIsSubmitting = (loaderType: TNameDescriptionLoader) => {
updateTeamspaceNameDescriptionLoader(teamspaceId, loaderType);
@@ -80,17 +82,42 @@ export const TeamspaceDescriptionInput: FC<TeamspaceDescriptionInputProps> = obs
id: teamspaceId,
description_html: initialValue === "" ? "<p></p>" : initialValue,
});
hasUnsavedChanges.current = false;
}, [initialValue, teamspaceId, reset]);
// ADDING handleDescriptionFormSubmit TO DEPENDENCY ARRAY PRODUCES ADVERSE EFFECTS
// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedFormSave = useCallback(
debounce(async () => {
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
handleSubmit(handleDescriptionFormSubmit)().finally(() => {
setIsSubmitting("submitted");
hasUnsavedChanges.current = false;
});
}, 1500),
[handleSubmit, teamspaceId]
);
// Save on unmount if there are unsaved changes
useEffect(
() => () => {
debouncedFormSave.cancel();
if (hasUnsavedChanges.current) {
handleSubmit(handleDescriptionFormSubmit)()
.catch((error) => {
console.error("Failed to save description on unmount:", error);
})
.finally(() => {
setIsSubmitting("submitted");
hasUnsavedChanges.current = false;
});
}
},
// since we don't want to save on unmount if there are no unsaved changes, no deps are needed
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
return (
<>
{localTeamspaceDescription.description_html ? (
@@ -109,6 +136,7 @@ export const TeamspaceDescriptionInput: FC<TeamspaceDescriptionInputProps> = obs
onChange={(description_json: object, description_html: string) => {
setIsSubmitting("submitting");
onChange(description_html);
hasUnsavedChanges.current = true;
setValue("description_json", description_json);
debouncedFormSave();
}}