fix(api): ensure files array is never null in API response

Add null safety checks to prevent TypeError when backend returns null
instead of empty array for files list. Initialize empty slices on backend
and add null coalescing on frontend when accessing files state.

- Backend: Initialize files slice to always return [] instead of null
- Frontend: Add null checks for files state in all map/filter operations
This commit is contained in:
2026-02-06 08:59:11 -05:00
parent a80de1730c
commit 10f584f9a8
3 changed files with 15 additions and 10 deletions

View File

@@ -52,6 +52,11 @@ func (h *Handlers) ListFiles(w http.ResponseWriter, r *http.Request) {
return return
} }
// Ensure we always encode an array, never null
if files == nil {
files = []*storage.File{}
}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(files) json.NewEncoder(w).Encode(files)
} }

View File

@@ -44,23 +44,23 @@ func (s *Storage) List() ([]*File, error) {
return nil, fmt.Errorf("failed to read directory: %w", err) return nil, fmt.Errorf("failed to read directory: %w", err)
} }
var files []*File files := make([]*File, 0)
for _, entry := range entries { for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
continue continue
} }
info, err := entry.Info() info, err := entry.Info()
if err != nil { if err != nil {
continue continue
} }
files = append(files, &File{ files = append(files, &File{
Name: entry.Name(), Name: entry.Name(),
Modified: info.ModTime().Unix(), Modified: info.ModTime().Unix(),
}) })
} }
logging.Logger.WithField("count", len(files)).Info("Listed files") logging.Logger.WithField("count", len(files)).Info("Listed files")
return files, nil return files, nil
} }

View File

@@ -20,7 +20,7 @@ function App() {
try { try {
setError(null); setError(null);
const fetchedFiles = await api.list(); const fetchedFiles = await api.list();
setFiles(fetchedFiles); setFiles(fetchedFiles ?? []);
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load files'); setError(err instanceof Error ? err.message : 'Failed to load files');
} finally { } finally {
@@ -56,7 +56,7 @@ function App() {
try { try {
setError(null); setError(null);
const file = await api.create(name, '# ' + name.replace('.md', '') + '\n\nStart writing here...'); const file = await api.create(name, '# ' + name.replace('.md', '') + '\n\nStart writing here...');
setFiles([...files, file]); setFiles([...(files ?? []), file]);
setSelectedFile(file); setSelectedFile(file);
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create file'); setError(err instanceof Error ? err.message : 'Failed to create file');
@@ -72,7 +72,7 @@ function App() {
const updated = await api.update(selectedFile.name, content); const updated = await api.update(selectedFile.name, content);
setSelectedFile(updated); setSelectedFile(updated);
setOriginalContent(content); setOriginalContent(content);
setFiles(files.map((f) => (f.name === updated.name ? updated : f))); setFiles((files ?? []).map((f) => (f.name === updated.name ? updated : f)));
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : 'Failed to save file'); setError(err instanceof Error ? err.message : 'Failed to save file');
} finally { } finally {
@@ -86,7 +86,7 @@ function App() {
try { try {
setError(null); setError(null);
await api.delete(name); await api.delete(name);
setFiles(files.filter((f) => f.name !== name)); setFiles((files ?? []).filter((f) => f.name !== name));
if (selectedFile?.name === name) { if (selectedFile?.name === name) {
setSelectedFile(null); setSelectedFile(null);
} }
@@ -125,7 +125,7 @@ function App() {
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
<div className="lg:col-span-1"> <div className="lg:col-span-1">
<FileList <FileList
files={files} files={files ?? []}
selectedFile={selectedFile?.name || null} selectedFile={selectedFile?.name || null}
onSelectFile={handleSelectFile} onSelectFile={handleSelectFile}
onDeleteFile={handleDeleteFile} onDeleteFile={handleDeleteFile}
@@ -163,7 +163,7 @@ function App() {
isOpen={showNewDialog} isOpen={showNewDialog}
onClose={() => setShowNewDialog(false)} onClose={() => setShowNewDialog(false)}
onCreate={handleCreateFile} onCreate={handleCreateFile}
existingNames={files.map((f) => f.name)} existingNames={files?.map((f) => f.name) ?? []}
/> />
{hasUnsavedChanges && ( {hasUnsavedChanges && (