Skip to content

Commit c626261

Browse files
authored
Fix ExportImageDialog infinite re-render loop (stan-smith#98)
* fix: eliminate re-render loop causing export failures - Remove onModelUpdated callback that triggered infinite re-render cycles - Replace debounce hack with controlled useEffect-based export timing - Ensure DOM stability before export execution to prevent null containerRef - Add isExporting guard to prevent concurrent export operations Resolves issue where image export functionality was completely broken due to infinite re-rendering caused by onModelUpdated callback. Fixes stan-smith#84 Huge thanks are attributed to @jibbex for resolving this issue!
1 parent 627fe2e commit c626261

File tree

1 file changed

+45
-28
lines changed

1 file changed

+45
-28
lines changed

packages/fossflow-lib/src/components/ExportImageDialog/ExportImageDialog.tsx

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ interface Props {
4040

4141
export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
4242
const containerRef = useRef<HTMLDivElement>();
43-
const debounceRef = useRef<NodeJS.Timeout>();
43+
const isExporting = useRef<boolean>(false);
4444
const currentView = useUiStateStore((state) => {
4545
return state.view;
4646
});
@@ -65,33 +65,24 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
6565
});
6666
}, [uiStateActions]);
6767

68-
const exportImage = useCallback(async () => {
69-
if (!containerRef.current) return;
70-
71-
clearTimeout(debounceRef.current);
72-
debounceRef.current = setTimeout(() => {
73-
exportAsImage(containerRef.current as HTMLDivElement)
74-
.then((data) => {
75-
return setImageData(data);
76-
})
77-
.catch((err) => {
78-
console.log(err);
79-
setExportError(true);
80-
});
81-
}, 2000);
68+
const exportImage = useCallback(() => {
69+
if (!containerRef.current || isExporting.current) {
70+
return;
71+
}
72+
73+
isExporting.current = true;
74+
exportAsImage(containerRef.current as HTMLDivElement)
75+
.then((data) => {
76+
setImageData(data);
77+
isExporting.current = false;
78+
})
79+
.catch((err) => {
80+
console.error(err);
81+
setExportError(true);
82+
isExporting.current = false;
83+
});
8284
}, []);
8385

84-
const downloadFile = useCallback(() => {
85-
if (!imageData) return;
86-
87-
const data = base64ToBlob(
88-
imageData.replace('data:image/png;base64,', ''),
89-
'image/png;charset=utf-8'
90-
);
91-
92-
downloadFileUtil(data, generateGenericFilename('png'));
93-
}, [imageData]);
94-
9586
const [showGrid, setShowGrid] = useState(false);
9687
const handleShowGridChange = (checked: boolean) => {
9788
setShowGrid(checked);
@@ -104,10 +95,37 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
10495
setBackgroundColor(color);
10596
};
10697

98+
// Reset image data when options change and trigger export
10799
useEffect(() => {
108100
setImageData(undefined);
101+
setExportError(false);
102+
isExporting.current = false;
103+
const timer = setTimeout(() => {
104+
exportImage();
105+
}, 100);
106+
107+
return () => clearTimeout(timer);
109108
}, [showGrid, backgroundColor]);
110109

110+
useEffect(() => {
111+
const timer = setTimeout(() => {
112+
exportImage();
113+
}, 100);
114+
115+
return () => clearTimeout(timer);
116+
}, []);
117+
118+
const downloadFile = useCallback(() => {
119+
if (!imageData) return;
120+
121+
const data = base64ToBlob(
122+
imageData.replace('data:image/png;base64,', ''),
123+
'image/png;charset=utf-8'
124+
);
125+
126+
downloadFileUtil(data, generateGenericFilename('png'));
127+
}, [imageData]);
128+
111129
return (
112130
<Dialog open onClose={onClose}>
113131
<DialogTitle>Export as image</DialogTitle>
@@ -146,7 +164,6 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
146164
>
147165
<Isoflow
148166
editorMode="NON_INTERACTIVE"
149-
onModelUpdated={exportImage}
150167
initialData={{
151168
...model,
152169
fitToView: true,
@@ -235,4 +252,4 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
235252
</DialogContent>
236253
</Dialog>
237254
);
238-
};
255+
};

0 commit comments

Comments
 (0)