Files
claudecodeui/src/components/code-editor/hooks/useCodeEditorDocument.ts
Haileyesus 4178ccdd5e fix(code-editor): stabilize document loading deps and replace blocking save alerts
The code editor document hook was reloading more often than necessary because
`useEffect` depended on the full `file` object (and `projectPath`, which was not
used in the effect body). Save flow also emitted verbose production debug logs
and used a blocking `alert()` for errors.

Changes:
- Refactored `useCodeEditorDocument` to derive stable file primitives:
  `fileProjectName`, `filePath`, `fileName`, `fileDiffNewString`,
  `fileDiffOldString`.
- Updated `loadFileContent` to use those stable fields while preserving existing
  diff-first loading logic (`file.diffInfo`, `new_string`, `old_string`).
- Replaced the effect dependency array with only stable primitive values used by
  the effect and removed `projectPath` from dependencies.
- Removed debug `console.log` calls in `handleSave`:
  - "Saving file:"
  - "Save response:"
  - "Save successful:"
- Kept error logging via `console.error` for failure diagnostics.
- Replaced blocking `alert()` on save failure with non-blocking UI feedback:
  - Added local `saveError` state in the hook.
  - Set `saveError` from `getErrorMessage(error)` in the save catch path.
  - Exposed `saveError` to `CodeEditor` and rendered an inline error banner.
- Preserved save lifecycle behavior:
  - `setSaving(false)` still runs in `finally`.
  - `setSaveSuccess(true)` and 2s timeout reset behavior remain unchanged.
2026-02-23 09:35:59 +03:00

119 lines
3.5 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import { api } from '../../../utils/api';
import type { CodeEditorFile } from '../types/types';
type UseCodeEditorDocumentParams = {
file: CodeEditorFile;
projectPath?: string;
};
const getErrorMessage = (error: unknown) => {
if (error instanceof Error) {
return error.message;
}
return String(error);
};
export const useCodeEditorDocument = ({ file }: UseCodeEditorDocumentParams) => {
const [content, setContent] = useState('');
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [saveSuccess, setSaveSuccess] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
const fileProjectName = file.projectName;
const filePath = file.path;
const fileName = file.name;
const fileDiffNewString = file.diffInfo?.new_string;
const fileDiffOldString = file.diffInfo?.old_string;
useEffect(() => {
const loadFileContent = async () => {
try {
setLoading(true);
// Diff payload may already include full old/new snapshots, so avoid disk read.
if (file.diffInfo && fileDiffNewString !== undefined && fileDiffOldString !== undefined) {
setContent(fileDiffNewString);
setLoading(false);
return;
}
const response = await api.readFile(fileProjectName, filePath);
if (!response.ok) {
throw new Error(`Failed to load file: ${response.status} ${response.statusText}`);
}
const data = await response.json();
setContent(data.content);
} catch (error) {
const message = getErrorMessage(error);
console.error('Error loading file:', error);
setContent(`// Error loading file: ${message}\n// File: ${fileName}\n// Path: ${filePath}`);
} finally {
setLoading(false);
}
};
loadFileContent();
}, [fileDiffNewString, fileDiffOldString, fileName, filePath, fileProjectName]);
const handleSave = useCallback(async () => {
setSaving(true);
setSaveError(null);
try {
const response = await api.saveFile(fileProjectName, filePath, content);
if (!response.ok) {
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
const errorData = await response.json();
throw new Error(errorData.error || `Save failed: ${response.status}`);
}
const textError = await response.text();
console.error('Non-JSON error response:', textError);
throw new Error(`Save failed: ${response.status} ${response.statusText}`);
}
await response.json();
setSaveSuccess(true);
setTimeout(() => setSaveSuccess(false), 2000);
} catch (error) {
const message = getErrorMessage(error);
console.error('Error saving file:', error);
setSaveError(message);
} finally {
setSaving(false);
}
}, [content, filePath, fileProjectName]);
const handleDownload = useCallback(() => {
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = file.name;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
URL.revokeObjectURL(url);
}, [content, file.name]);
return {
content,
setContent,
loading,
saving,
saveSuccess,
saveError,
handleSave,
handleDownload,
};
};