From 4b066706a5ff67a5743b6f5b7114591754126df4 Mon Sep 17 00:00:00 2001
From: Erik Tiekstra
Date: Tue, 25 Nov 2025 11:26:02 +0100
Subject: [PATCH] fix(BOOK-478): Fixed issue with running handleMedia inside a
useEffect which is causing an infinite loop
---
remix/app/components/ImageVaultDAM.tsx | 28 ++++++++++----------------
remix/app/routes/field.tsx | 17 ++++++++++++----
rte/components/ImageElement.tsx | 17 ++++++++--------
3 files changed, 32 insertions(+), 30 deletions(-)
diff --git a/remix/app/components/ImageVaultDAM.tsx b/remix/app/components/ImageVaultDAM.tsx
index 196264c..552e41b 100644
--- a/remix/app/components/ImageVaultDAM.tsx
+++ b/remix/app/components/ImageVaultDAM.tsx
@@ -7,16 +7,12 @@ import {
FieldLabel,
cbModal,
} from "@contentstack/venus-components"
-import {
- getImageVaultAssetFromData,
- isInsertResponse,
- openImageVault,
-} from "~/utils/imagevault"
+import { isInsertResponse, openImageVault } from "~/utils/imagevault"
import ImageEditModal from "./ImageEditModal"
import type UiLocation from "@contentstack/app-sdk/dist/src/uiLocation"
import type { CbModalProps } from "@contentstack/venus-components/build/components/Modal/Modal"
-import type { ImageVaultAsset, InsertResponse } from "~/types/imagevault"
+import type { ImageVaultAsset } from "~/types/imagevault"
import type { Lang } from "~/types/lang"
import type {
EntryDataPublishDetails,
@@ -27,7 +23,7 @@ import FullSizeImage from "./FullSizeImage"
export type ImageVaultDAMProps = {
sdk: UiLocation
config: ImageVaultDAMConfig
- initialData: ImageVaultAsset | InsertResponse | null
+ initialData: ImageVaultAsset | null
}
type DAMButtonProps = { onClick: () => void }
@@ -117,8 +113,7 @@ export default function ImageVaultDAM({
config,
initialData,
}: ImageVaultDAMProps) {
- const imageVaultAsset = getImageVaultAssetFromData(initialData)
- const [media, setMedia] = useState(imageVaultAsset)
+ const [media, setMedia] = useState(initialData)
const field = sdk.location.CustomField?.field
const frame = sdk.location.CustomField?.frame
@@ -176,18 +171,17 @@ export default function ImageVaultDAM({
updateFrameHeight()
}, [updateFrameHeight])
- // The existing data might still be in InsertResponse format if the user has not edited it yet.
- // We'll convert it to ImageVaultAsset when the user edits the the emtry.
- useEffect(() => {
- if (isInsertResponse(initialData) && imageVaultAsset) {
- handleMedia(imageVaultAsset)
- }
- }, [initialData, imageVaultAsset, handleMedia])
-
if (!field || !frame || !entry || !stack) {
return Initializing custom field...
}
+ // The existing data might still be in InsertResponse format if the user has not edited it yet.
+ // We'll convert it to ImageVaultAsset when the component mounts.
+ const fieldData = field.getData()
+ if (isInsertResponse(fieldData)) {
+ field.setData(initialData)
+ }
+
const entryData: EntryDataPublishDetails = {
//TODO: Add support for branches
branch: "main",
diff --git a/remix/app/routes/field.tsx b/remix/app/routes/field.tsx
index 36ed3db..91b47ad 100644
--- a/remix/app/routes/field.tsx
+++ b/remix/app/routes/field.tsx
@@ -9,8 +9,11 @@ import InvalidConfig from "~/components/InvalidConfig"
import { GenericObjectType } from "@contentstack/app-sdk/dist/src/types/common.types"
import UiLocation from "@contentstack/app-sdk/dist/src/uiLocation"
-import type { InsertResponse } from "~/types/imagevault"
-import { isImageVaultDAMConfig } from "~/utils/imagevault"
+import type { ImageVaultAsset, InsertResponse } from "~/types/imagevault"
+import {
+ getImageVaultAssetFromData,
+ isImageVaultDAMConfig,
+} from "~/utils/imagevault"
const ImageVaultDAM = lazy(() => import("~/components/ImageVaultDAM"))
@@ -25,7 +28,9 @@ function FieldContent({ sdk, appConfig }: FieldContentProps) {
)
const [showDisclaimer, setShowDisclaimer] = useState(false)
- const [fieldData, setFieldData] = useState()
+ const [fieldData, setFieldData] = useState<
+ InsertResponse | ImageVaultAsset | null
+ >()
const [dataIsLoaded, setDataIsLoaded] = useState(false)
const entry = sdk?.location.CustomField?.entry
@@ -76,9 +81,13 @@ function FieldContent({ sdk, appConfig }: FieldContentProps) {
const fieldConfig = sdk.location.CustomField?.fieldConfig
const config = { ...appConfig, ...fieldConfig }
+ // The existing data might still be in InsertResponse format if the user has not edited it yet.
+ // We'll convert it to ImageVaultAsset when the component mounts.
+ const imageVaultAsset = getImageVaultAssetFromData(initialData)
+
return (
Loading field...
}>
-
+
)
}
diff --git a/rte/components/ImageElement.tsx b/rte/components/ImageElement.tsx
index 9c2065c..d970284 100644
--- a/rte/components/ImageElement.tsx
+++ b/rte/components/ImageElement.tsx
@@ -1,5 +1,5 @@
import { Icon, Tooltip, cbModal } from "@contentstack/venus-components"
-import React, { PropsWithChildren, useCallback, useEffect } from "react"
+import React, { PropsWithChildren, useCallback } from "react"
import EmbedBtn from "./EmbedBtn"
import ImageEditModal from "./ImageEditModal"
@@ -19,6 +19,7 @@ type ImageElementProps = PropsWithChildren & {
rte: IRteParam
}
export function ImageElement({ children, element, rte }: ImageElementProps) {
+ const assetIsInsertResponse = isInsertResponse(element.attrs)
const imageVaultAsset = getImageVaultAssetFromData(element.attrs)
const isSelected = rte.selection.isSelected()
const isFocused = rte.selection.isFocused()
@@ -62,14 +63,6 @@ export function ImageElement({ children, element, rte }: ImageElementProps) {
})
}, [element, handleMedia])
- // The existing data might still be in InsertResponse format if the user has not edited it yet.
- // We'll convert it to ImageVaultAsset when the user edits the RTE.
- useEffect(() => {
- if (isInsertResponse(element.attrs) && imageVaultAsset) {
- handleMedia(imageVaultAsset)
- }
- }, [element.attrs, imageVaultAsset, handleMedia])
-
const ToolTipButtons = () => {
return (
@@ -92,6 +85,12 @@ export function ImageElement({ children, element, rte }: ImageElementProps) {
return <>{children}>
}
+ // The existing data might still be in InsertResponse format if the user has not edited it yet.
+ // We'll convert it to ImageVaultAsset when the user edits the RTE.
+ if (assetIsInsertResponse) {
+ handleMedia(imageVaultAsset)
+ }
+
return (