Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2025-02-28 - [Concurrent Data Fetching]
**Learning:** Sequential data fetching can significantly increase page load latency. Combining independent fetch requests using `Promise.all` allows them to process concurrently, preventing network waterfalls.
**Action:** When implementing page components that require multiple independent data streams, analyze if they can be fetched in parallel using `Promise.all` rather than using sequential `await fetch()` calls.
78 changes: 40 additions & 38 deletions src/app/projects/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,50 +49,52 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
return;
}

if (isSignedIn && projectId) {
fetchProjectAndLists();
}
}, [isLoaded, isSignedIn, projectId, router]);

const fetchProjectAndLists = async () => {
try {
setIsLoading(true);

// Fetch project details
const projectResponse = await fetch(`/api/projects/${projectId}`);
const projectResult = await projectResponse.json();

if (!projectResult.success) {
setError(projectResult.error || "Project not found");
return;
}
const fetchProjectAndLists = async () => {
try {
setIsLoading(true);

// Fetch data concurrently to reduce page load latency
const [projectResponse, listsResponse, allListsResponse] = await Promise.all([
fetch(`/api/projects/${projectId}`),
fetch(`/api/projects/${projectId}/lists`),
fetch("/api/lists")
]);

// Parse JSON concurrently
const [projectResult, listsResult, allListsResult] = await Promise.all([
projectResponse.json(),
listsResponse.json(),
allListsResponse.json()
]);
Comment on lines +56 to +68
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Promise.all for all three fetches makes the whole load fail if any request rejects or JSON parsing fails (e.g., /api/lists transient 500), which is a behavior regression vs the prior sequential flow where the project could still render and only lists would be missing. Consider fetching/parsing the project first (or using Promise.allSettled) and only treating the project request as fatal; handle lists/all-lists failures independently (e.g., keep project visible and show a non-blocking error banner).

Copilot uses AI. Check for mistakes.

if (!projectResult.success) {
setError(projectResult.error || "Project not found");
return;
}

setProject(projectResult.data);
setEditName(projectResult.data.name);
setEditDescription(projectResult.data.description || "");
setProject(projectResult.data);
setEditName(projectResult.data.name);
setEditDescription(projectResult.data.description || "");

// Fetch lists in project
const listsResponse = await fetch(`/api/projects/${projectId}/lists`);
const listsResult = await listsResponse.json();
if (listsResult.success) {
setLists(listsResult.data);
}

if (listsResult.success) {
setLists(listsResult.data);
if (allListsResult.success) {
setAllLists(allListsResult.data);
}
} catch (err) {
console.error("Error fetching project:", err);
setError("Failed to load project");
} finally {
setIsLoading(false);
}
};

// Fetch all user lists (for adding to project)
const allListsResponse = await fetch("/api/lists");
const allListsResult = await allListsResponse.json();

if (allListsResult.success) {
setAllLists(allListsResult.data);
}
} catch (err) {
console.error("Error fetching project:", err);
setError("Failed to load project");
} finally {
setIsLoading(false);
if (isSignedIn && projectId) {
fetchProjectAndLists();
}
};
}, [isLoaded, isSignedIn, projectId, router]);

const handleUpdateProject = async () => {
if (!editName.trim()) {
Expand Down
Loading