Fix React error #185 when copy-pasting content between blocks (#SKY-7638) (#4646)

This commit is contained in:
Celal Zamanoglu
2026-02-06 01:39:25 +03:00
committed by GitHub
parent 435192908c
commit 7a82176ab2
2 changed files with 71 additions and 17 deletions

View File

@@ -1,6 +1,6 @@
import { Textarea } from "@/components/ui/textarea";
import type { ChangeEventHandler, HTMLAttributes } from "react";
import { forwardRef, useEffect, useRef, useCallback } from "react";
import { forwardRef, useRef, useCallback, useLayoutEffect } from "react";
import { cn } from "@/util/utils";
type Props = {
@@ -30,6 +30,7 @@ const AutoResizingTextarea = forwardRef<HTMLTextAreaElement, Props>(
forwardedRef,
) => {
const innerRef = useRef<HTMLTextAreaElement | null>(null);
const lastHeightRef = useRef<string>("");
const getTextarea = useCallback(() => innerRef.current, []);
const setRefs = (element: HTMLTextAreaElement | null) => {
@@ -43,13 +44,25 @@ const AutoResizingTextarea = forwardRef<HTMLTextAreaElement, Props>(
}
};
useEffect(() => {
useLayoutEffect(() => {
const textareaElement = getTextarea();
if (!textareaElement) {
return;
}
// Temporarily set to auto to measure scrollHeight accurately
textareaElement.style.height = "auto";
textareaElement.style.height = `${textareaElement.scrollHeight + 2}px`;
const newHeight = `${textareaElement.scrollHeight + 2}px`;
// Only apply the final height if it differs from the last applied height
// This prevents unnecessary dimension change events in React Flow
if (lastHeightRef.current !== newHeight) {
lastHeightRef.current = newHeight;
textareaElement.style.height = newHeight;
} else {
// Restore the previous height since nothing changed
textareaElement.style.height = lastHeightRef.current;
}
}, [getTextarea, value]);
return (

View File

@@ -36,6 +36,7 @@ import {
import "@xyflow/react/dist/style.css";
import { nanoid } from "nanoid";
import { useCallback, useEffect, useRef, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import { useBlocker, useParams } from "react-router-dom";
import {
AWSSecretParameter,
@@ -321,6 +322,9 @@ function FlowRenderer({
// Track if this is the initial load to prevent false "unsaved changes" detection
const isInitialLoadRef = useRef(true);
// Track if we're currently in a layout operation to prevent infinite loops
const isLayoutingRef = useRef(false);
useEffect(() => {
if (nodesInitialized) {
setShouldConstrainPan(true);
@@ -364,6 +368,27 @@ function FlowRenderer({
[setNodes, setEdges, targettedBlockLabel],
);
// Debounced layout for dimension changes to prevent infinite loops (React error #185)
// when copy-pasting triggers rapid successive dimension changes
const debouncedLayoutForDimensions = useDebouncedCallback(
(tempNodes: Array<AppNode>, currentEdges: Array<Edge>) => {
if (isLayoutingRef.current) {
return;
}
isLayoutingRef.current = true;
try {
doLayout(tempNodes, currentEdges);
} finally {
// Reset the flag after a short delay to allow React to flush updates
requestAnimationFrame(() => {
isLayoutingRef.current = false;
});
}
},
50,
{ leading: true, trailing: true, maxWait: 200 },
);
useEffect(() => {
if (nodesInitialized) {
doLayout(nodes, edges);
@@ -850,21 +875,37 @@ function FlowRenderer({
const dimensionChanges = changes.filter(
(change) => change.type === "dimensions",
);
const tempNodes = [...nodes];
dimensionChanges.forEach((change) => {
const node = tempNodes.find((node) => node.id === change.id);
if (node) {
if (node.measured?.width) {
node.measured.width = change.dimensions?.width;
}
if (node.measured?.height) {
node.measured.height = change.dimensions?.height;
}
}
});
if (dimensionChanges.length > 0) {
doLayout(tempNodes, edges);
// Only process dimension changes if we're not already in a layout operation
// This prevents infinite loops (React error #185) during copy-paste
if (dimensionChanges.length > 0 && !isLayoutingRef.current) {
const tempNodes = [...nodes];
let hasActualChanges = false;
dimensionChanges.forEach((change) => {
const node = tempNodes.find((node) => node.id === change.id);
if (node) {
const newWidth = change.dimensions?.width;
const newHeight = change.dimensions?.height;
// Only update if dimensions actually changed
if (
node.measured?.width !== newWidth ||
node.measured?.height !== newHeight
) {
hasActualChanges = true;
if (node.measured) {
node.measured.width = newWidth;
node.measured.height = newHeight;
}
}
}
});
// Only trigger layout if there were actual dimension changes
if (hasActualChanges) {
debouncedLayoutForDimensions(tempNodes, edges);
}
}
// Only track changes after initial load is complete and not during internal updates