From 10f584f9a8404f73f74db865c09887f5676b2cc1 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Fri, 6 Feb 2026 08:59:11 -0500 Subject: [PATCH] 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 --- backend/internal/api/handlers.go | 5 +++++ backend/internal/storage/storage.go | 8 ++++---- frontend/src/App.tsx | 12 ++++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/backend/internal/api/handlers.go b/backend/internal/api/handlers.go index db1adcb..d654c94 100644 --- a/backend/internal/api/handlers.go +++ b/backend/internal/api/handlers.go @@ -52,6 +52,11 @@ func (h *Handlers) ListFiles(w http.ResponseWriter, r *http.Request) { return } + // Ensure we always encode an array, never null + if files == nil { + files = []*storage.File{} + } + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(files) } diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index 30032ad..d657ed2 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -44,23 +44,23 @@ func (s *Storage) List() ([]*File, error) { return nil, fmt.Errorf("failed to read directory: %w", err) } - var files []*File + files := make([]*File, 0) for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") { continue } - + info, err := entry.Info() if err != nil { continue } - + files = append(files, &File{ Name: entry.Name(), Modified: info.ModTime().Unix(), }) } - + logging.Logger.WithField("count", len(files)).Info("Listed files") return files, nil } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ab09ef2..7f2b85c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -20,7 +20,7 @@ function App() { try { setError(null); const fetchedFiles = await api.list(); - setFiles(fetchedFiles); + setFiles(fetchedFiles ?? []); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load files'); } finally { @@ -56,7 +56,7 @@ function App() { try { setError(null); const file = await api.create(name, '# ' + name.replace('.md', '') + '\n\nStart writing here...'); - setFiles([...files, file]); + setFiles([...(files ?? []), file]); setSelectedFile(file); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create file'); @@ -72,7 +72,7 @@ function App() { const updated = await api.update(selectedFile.name, content); setSelectedFile(updated); setOriginalContent(content); - setFiles(files.map((f) => (f.name === updated.name ? updated : f))); + setFiles((files ?? []).map((f) => (f.name === updated.name ? updated : f))); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save file'); } finally { @@ -86,7 +86,7 @@ function App() { try { setError(null); await api.delete(name); - setFiles(files.filter((f) => f.name !== name)); + setFiles((files ?? []).filter((f) => f.name !== name)); if (selectedFile?.name === name) { setSelectedFile(null); } @@ -125,7 +125,7 @@ function App() {
setShowNewDialog(false)} onCreate={handleCreateFile} - existingNames={files.map((f) => f.name)} + existingNames={files?.map((f) => f.name) ?? []} /> {hasUnsavedChanges && (