Skip to content

Full swr implementation#5

Merged
Ross1116 merged 2 commits into
mainfrom
staging
Feb 11, 2026
Merged

Full swr implementation#5
Ross1116 merged 2 commits into
mainfrom
staging

Conversation

@Ross1116

@Ross1116 Ross1116 commented Feb 11, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Implemented cross-tab project synchronization for seamless multi-tab experience.
  • Performance

    • Optimized component rendering with memoization to reduce unnecessary re-renders.
    • Improved browser event handling efficiency with passive listeners.
  • Refactor

    • Modernized data fetching infrastructure with enhanced caching and revalidation strategies.
    • Consolidated error handling and loading states across dashboard pages.
  • Chores

    • Updated production checklist with expanded infrastructure and security categories.

@vercel

vercel Bot commented Feb 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sitespace-app Ready Ready Preview, Comment Feb 11, 2026 5:29am

@coderabbitai

coderabbitai Bot commented Feb 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request introduces SWR (stale-while-revalidate) as the primary data-fetching library, replacing manual API calls and localStorage-based caching across multiple dashboard pages. It also adds React.memo optimizations, removes the legacy useSmartRefresh utility, updates event listeners to passive mode, and restructures the production checklist into consolidated categories.

Changes

Cohort / File(s) Summary
SWR Infrastructure Setup
package.json, src/lib/swr.ts
Added SWR dependency (^2.4.0) and created new SWR utilities (swrFetcher, SWR_CONFIG) with 5-minute refresh interval and 30-second deduplication.
Dashboard Pages SWR Migration
src/app/(dashboard)/assets/page.tsx, src/app/(dashboard)/home/page.tsx, src/app/(dashboard)/subcontractors/page.tsx
Replaced manual API fetching, AbortController logic, and localStorage caching with SWR hooks; removed isAbortError helpers; updated mutation flows to use SWR mutate; consolidated error messages.
Booking Components
src/components/bookings/BookingCardDesktop.tsx, src/components/bookings/BookingCardMobile.tsx
Wrapped components with React.memo for performance optimization; no behavioral changes to props or logic.
Bookings Page & Forms SWR Migration
src/components/bookings/BookingsPage.tsx, src/components/forms/CreateBookingForm.tsx
Migrated from direct API client and localStorage-based asset/subcontractor loading to SWR-based fetching; updated data transformations and error handling; replaced refresh logic with SWR mutate calls.
Calendar Integration Refactoring
src/components/multicalendar/MulticalendarPage.tsx
Replaced direct API calls and interval-based polling with SWR hooks; removed caching logic; introduced processBookingToEvent helper; streamlined state management via SWR-derived data.
Performance & Cleanup
src/app/context/AuthContext.tsx, src/lib/useSmartRefresh.ts
Added passive event listeners for activity detection (non-blocking); removed legacy useSmartRefresh utility entirely (SWR now handles refresh orchestration).
Documentation Update
PRODUCTION_CHECKLIST.md
Consolidated granular checklist entries into high-level categories (Infrastructure, Security, Data Fetching, Error Handling, etc.); added backlog items for database backups, Sentry alerts, and soft launch testing.

Sequence Diagram

sequenceDiagram
    participant Component as Dashboard Page
    participant SWR as SWR Hook
    participant Fetcher as swrFetcher
    participant API as API Client
    participant Backend as Backend Server
    
    Component->>SWR: useSWR(key, swrFetcher, SWR_CONFIG)
    SWR->>Fetcher: Fetch triggered (on mount/revalidate)
    Fetcher->>API: api.get(url)
    API->>Backend: HTTP GET request
    Backend-->>API: Response data
    API-->>Fetcher: r.data
    Fetcher-->>SWR: Return data
    SWR-->>Component: {data, isLoading, mutate}
    
    Note over Component: Component renders with SWR data
    
    Component->>Component: User action (save/delete/update)
    Component->>SWR: mutate() to trigger refresh
    SWR->>Fetcher: Refetch with same key
    Fetcher->>API: api.get(url)
    API->>Backend: HTTP GET request
    Backend-->>API: Updated data
    API-->>Fetcher: r.data
    Fetcher-->>SWR: Return updated data
    SWR-->>Component: {data (refreshed), isLoading, mutate}
    Component->>Component: Re-render with fresh data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hops through code with SWR grace,
Caching patterns find their place,
No more fetching, no more wait,
Stale-while-revalidate!
Data flows like fresh carrot treats—
Performance now completes! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Full swr implementation' directly describes the main change: replacing manual API fetching and caching with SWR (stale-while-revalidate) across multiple components and files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch staging

Comment @coderabbitai help to get the list of available commands and usage tips.

@Ross1116 Ross1116 merged commit d4e2ab4 into main Feb 11, 2026
3 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/app/`(dashboard)/home/page.tsx:
- Around line 98-107: In the useMemo that builds projects from projectsRaw
(inside the projects constant), avoid spreading backend fields after you set
normalized values; change the object construction for SubcontractorProjectApi
cases to spread p first and then explicitly set id, name, location, and status
so backend keys like id (assignment id) cannot overwrite the normalized
id/project_name mapping; update the map callback that returns ({ ...p, id:
p.project_id, name: p.project_name, location: p.project_location, status:
p.is_active ? "active" : "inactive" }) to enforce the correct values.

In `@src/components/forms/CreateBookingForm.tsx`:
- Around line 556-570: The effect currently dispatches SET_ASSET_ERROR whenever
assetsSwrError is truthy which clears assets even if assetsResponse (stale data)
exists; change the logic in the useEffect that reads assetsSwrError and
assetsResponse so you only dispatchAsset({ type: "SET_ASSET_ERROR", error: true
}) when assetsSwrError is truthy AND assetsResponse is falsy/empty, otherwise
preserve existing assets and only dispatch SET_ASSETS from the normalizedAssets
mapping; update the useEffect that references assetsSwrError, assetsResponse,
dispatchAsset, and AssetOption to implement this conditional check.
🧹 Nitpick comments (4)
src/app/context/AuthContext.tsx (1)

233-235: Implicit return in forEach callback.

The arrow function implicitly returns the result of addEventListener, which Biome flags as suspicious. While functionally harmless, wrapping in braces makes intent clearer:

🔧 Suggested fix
-    events.forEach((e) =>
-      window.addEventListener(e, onActivity, { passive: true }),
-    );
+    events.forEach((e) => {
+      window.addEventListener(e, onActivity, { passive: true });
+    });
src/lib/swr.ts (1)

4-4: Consider adding error handling or retry configuration.

The fetcher relies on SWR's default error handling. For production, you may want to consider adding onErrorRetry to SWR_CONFIG for customized retry behavior (e.g., no retry on 4xx errors).

💡 Optional: Add error retry configuration
 export const SWR_CONFIG: SWRConfiguration = {
   revalidateOnFocus: true,
   revalidateOnReconnect: true,
   refreshInterval: 5 * 60 * 1000, // 5 min
   dedupingInterval: 30_000, // 30s dedup
+  onErrorRetry: (error, _key, _config, revalidate, { retryCount }) => {
+    // Don't retry on 4xx errors
+    if (error?.response?.status >= 400 && error?.response?.status < 500) return;
+    // Max 3 retries
+    if (retryCount >= 3) return;
+    // Retry after 5s
+    setTimeout(() => revalidate({ retryCount }), 5000);
+  },
 };
src/components/multicalendar/MulticalendarPage.tsx (1)

97-107: Date range is fixed at mount time.

The date range is computed once when the component mounts (empty dependency array means it never recalculates). If a user keeps the page open for extended periods, the 45-day window won't shift with the current date.

This is likely acceptable for typical usage, but worth noting if users leave the dashboard open overnight.

src/app/(dashboard)/assets/page.tsx (1)

60-66: Consider importing AssetListResponse from shared types.

This interface duplicates AssetListResponse from src/types/index.ts. While not critical, importing from the shared types file would reduce duplication and ensure consistency.

💡 Optional: Import from shared types
-import { TransformedAsset, getApiErrorMessage } from "@/types";
+import { TransformedAsset, getApiErrorMessage, AssetListResponse } from "@/types";

-interface AssetListResponse {
-  assets: AssetFromBackend[];
-  total: number;
-  skip: number;
-  limit: number;
-  has_more: boolean;
-}

Note: You'd need to adjust the generic type or keep a local type alias if AssetListResponse uses ApiAsset instead of AssetFromBackend.

Comment on lines +98 to +107
const projects = useMemo<ApiProject[]>(() => {
if (!projectsRaw) return [];
if (user?.role === "subcontractor") {
setSubcontractorCount(0);
return;
return (projectsRaw as SubcontractorProjectApi[]).map((p) => ({
id: p.project_id,
name: p.project_name,
location: p.project_location,
status: p.is_active ? "active" : "inactive",
...p,
}));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent backend fields from overwriting normalized project IDs.
Spreading ...p after setting id/name/location can override them if the backend payload includes id (e.g., assignment id), which would break project scoping. Spread first, then override with the normalized fields.

🛠️ Suggested fix (normalize after spread)
 return (projectsRaw as SubcontractorProjectApi[]).map((p) => ({
-  id: p.project_id,
-  name: p.project_name,
-  location: p.project_location,
-  status: p.is_active ? "active" : "inactive",
-  ...p,
+  ...p,
+  id: p.project_id,
+  name: p.project_name,
+  location: p.project_location,
+  status: p.is_active ? "active" : "inactive",
 }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const projects = useMemo<ApiProject[]>(() => {
if (!projectsRaw) return [];
if (user?.role === "subcontractor") {
setSubcontractorCount(0);
return;
return (projectsRaw as SubcontractorProjectApi[]).map((p) => ({
id: p.project_id,
name: p.project_name,
location: p.project_location,
status: p.is_active ? "active" : "inactive",
...p,
}));
const projects = useMemo<ApiProject[]>(() => {
if (!projectsRaw) return [];
if (user?.role === "subcontractor") {
return (projectsRaw as SubcontractorProjectApi[]).map((p) => ({
...p,
id: p.project_id,
name: p.project_name,
location: p.project_location,
status: p.is_active ? "active" : "inactive",
}));
🤖 Prompt for AI Agents
In `@src/app/`(dashboard)/home/page.tsx around lines 98 - 107, In the useMemo that
builds projects from projectsRaw (inside the projects constant), avoid spreading
backend fields after you set normalized values; change the object construction
for SubcontractorProjectApi cases to spread p first and then explicitly set id,
name, location, and status so backend keys like id (assignment id) cannot
overwrite the normalized id/project_name mapping; update the map callback that
returns ({ ...p, id: p.project_id, name: p.project_name, location:
p.project_location, status: p.is_active ? "active" : "inactive" }) to enforce
the correct values.

Comment on lines 556 to +570
useEffect(() => {
const assetString = localStorage.getItem(`assets_${userId}`);
if (!assetString) {
if (assetsSwrError) {
dispatchAsset({ type: "SET_ASSET_ERROR", error: true });
return;
}
try {
const parsedAssets = JSON.parse(assetString) as ApiAsset[];
const normalizedAssets: AssetOption[] = parsedAssets.map((asset) => {
const cached = asset as ApiAsset & {
assetKey?: string;
assetTitle?: string;
};
return {
...asset,
assetKey: cached.assetKey ?? asset.id,
assetTitle: cached.assetTitle ?? asset.name,
};
});
dispatchAsset({ type: "SET_ASSETS", assets: normalizedAssets });
} catch (err) {
console.error("Error parsing assets:", err);
dispatchAsset({ type: "SET_ASSET_ERROR", error: true });
}
}, [userId]);
if (!assetsResponse) return;
const normalizedAssets: AssetOption[] = (assetsResponse.assets || []).map(
(asset) => ({
...asset,
assetKey: asset.id,
assetTitle: asset.name,
}),
);
dispatchAsset({ type: "SET_ASSETS", assets: normalizedAssets });
}, [assetsResponse, assetsSwrError]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "CreateBookingForm.tsx" -type f 2>/dev/null | head -20

Repository: Ross1116/sitespace-app

Length of output: 48


🏁 Script executed:

git ls-files | grep -i "createbookingform" | head -20

Repository: Ross1116/sitespace-app

Length of output: 108


🏁 Script executed:

find . -name "*.tsx" -path "*components/forms*" 2>/dev/null | head -20

Repository: Ross1116/sitespace-app

Length of output: 286


🏁 Script executed:

wc -l src/components/forms/CreateBookingForm.tsx

Repository: Ross1116/sitespace-app

Length of output: 113


🏁 Script executed:

sed -n '540,580p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 1611


🏁 Script executed:

grep -n "assetsResponse\|assetsSwrError" src/components/forms/CreateBookingForm.tsx | head -20

Repository: Ross1116/sitespace-app

Length of output: 330


🏁 Script executed:

grep -n "dispatchAsset\|SET_ASSET_ERROR\|SET_ASSETS" src/components/forms/CreateBookingForm.tsx | head -30

Repository: Ross1116/sitespace-app

Length of output: 683


🏁 Script executed:

grep -n "const dispatchAsset\|function.*dispatchAsset" src/components/forms/CreateBookingForm.tsx

Repository: Ross1116/sitespace-app

Length of output: 48


🏁 Script executed:

rg "useReducer.*asset" src/components/forms/CreateBookingForm.tsx -A 5

Repository: Ross1116/sitespace-app

Length of output: 48


🏁 Script executed:

sed -n '359,365p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 354


🏁 Script executed:

sed -n '165,175p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 560


🏁 Script executed:

sed -n '350,375p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 975


🏁 Script executed:

grep -n "assetError" src/components/forms/CreateBookingForm.tsx | head -20

Repository: Ross1116/sitespace-app

Length of output: 357


🏁 Script executed:

sed -n '1,50p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 1723


🏁 Script executed:

grep -n "useSWR\|import.*swr" src/components/forms/CreateBookingForm.tsx

Repository: Ross1116/sitespace-app

Length of output: 208


🏁 Script executed:

grep -r "useSWR" src --include="*.ts" --include="*.tsx" | head -5

Repository: Ross1116/sitespace-app

Length of output: 482


🏁 Script executed:

cat src/lib/swr.ts

Repository: Ross1116/sitespace-app

Length of output: 405


🏁 Script executed:

sed -n '905,915p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 653


🏁 Script executed:

sed -n '550,575p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 1021


🏁 Script executed:

sed -n '470,480p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 377


🏁 Script executed:

web_search
SWR stale-while-revalidate behavior error with cached data

Repository: Ross1116/sitespace-app

Length of output: 158


🌐 Web query:

SWR stale-while-revalidate error behavior stale data cached

💡 Result:

In SWR (the React data-fetching library), the “stale-while-revalidate” pattern means:

  • If there’s cached data for a key, SWR returns it immediately (“stale”) and starts a revalidation request in the background. (You can control this on mount via revalidateIfStale, revalidateOnMount, etc.) [1]
  • If that background revalidation request fails, SWR keeps the previously cached data and also exposes the error. In other words, data and error can both be set at the same time, so you can keep rendering the last good data while showing an error state. The cache is not overwritten by the failed response. [2]
  • Retries on error happen automatically (exponential backoff) unless you customize/disable them with onErrorRetry. [2]

If you instead meant HTTP/CDN caching headers (also often described as “stale-while-revalidate”):

  • Cache-Control: stale-while-revalidate=N allows a cache to serve a stale response while it revalidates in the background. [3]
  • Serving stale specifically when the origin errors is a different directive: stale-if-error=N. Without stale-if-error, an error from the origin is typically not replaced by stale content. [3]

Sources:
[1] SWR docs – Automatic Revalidation https://swr.vercel.app/docs/revalidation
[2] SWR docs – Error Handling https://swr.vercel.app/docs/error-handling
[3] RFC 5861 (stale-while-revalidate, stale-if-error) https://www.rfc-editor.org/rfc/rfc5861.html


🏁 Script executed:

sed -n '139,150p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 466


🏁 Script executed:

sed -n '135,165p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 1124


🏁 Script executed:

sed -n '350,375p' src/components/forms/CreateBookingForm.tsx | cat -n

Repository: Ross1116/sitespace-app

Length of output: 975


🏁 Script executed:

grep -n "assetError\|assets:" src/components/forms/CreateBookingForm.tsx | grep -A2 -B2 "908"

Repository: Ross1116/sitespace-app

Length of output: 197


Only set error when no data exists; preserve stale assets during transient SWR revalidation failures.

SWR can emit error while data remains available (stale-while-revalidate pattern). The current early return on assetsSwrError clears the asset cache via the SET_ASSET_ERROR reducer action, blocking selection even when stale assets are available. Only signal error when both error and no cached data exist.

🛠️ Suggested fix
 useEffect(() => {
-  if (assetsSwrError) {
-    dispatchAsset({ type: "SET_ASSET_ERROR", error: true });
-    return;
-  }
-  if (!assetsResponse) return;
+  if (assetsSwrError && !assetsResponse) {
+    dispatchAsset({ type: "SET_ASSET_ERROR", error: true });
+    return;
+  }
+  if (!assetsResponse) return;
   const normalizedAssets: AssetOption[] = (assetsResponse.assets || []).map(
     (asset) => ({
       ...asset,
       assetKey: asset.id,
       assetTitle: asset.name,
     }),
   );
   dispatchAsset({ type: "SET_ASSETS", assets: normalizedAssets });
 }, [assetsResponse, assetsSwrError]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const assetString = localStorage.getItem(`assets_${userId}`);
if (!assetString) {
if (assetsSwrError) {
dispatchAsset({ type: "SET_ASSET_ERROR", error: true });
return;
}
try {
const parsedAssets = JSON.parse(assetString) as ApiAsset[];
const normalizedAssets: AssetOption[] = parsedAssets.map((asset) => {
const cached = asset as ApiAsset & {
assetKey?: string;
assetTitle?: string;
};
return {
...asset,
assetKey: cached.assetKey ?? asset.id,
assetTitle: cached.assetTitle ?? asset.name,
};
});
dispatchAsset({ type: "SET_ASSETS", assets: normalizedAssets });
} catch (err) {
console.error("Error parsing assets:", err);
dispatchAsset({ type: "SET_ASSET_ERROR", error: true });
}
}, [userId]);
if (!assetsResponse) return;
const normalizedAssets: AssetOption[] = (assetsResponse.assets || []).map(
(asset) => ({
...asset,
assetKey: asset.id,
assetTitle: asset.name,
}),
);
dispatchAsset({ type: "SET_ASSETS", assets: normalizedAssets });
}, [assetsResponse, assetsSwrError]);
useEffect(() => {
if (assetsSwrError && !assetsResponse) {
dispatchAsset({ type: "SET_ASSET_ERROR", error: true });
return;
}
if (!assetsResponse) return;
const normalizedAssets: AssetOption[] = (assetsResponse.assets || []).map(
(asset) => ({
...asset,
assetKey: asset.id,
assetTitle: asset.name,
}),
);
dispatchAsset({ type: "SET_ASSETS", assets: normalizedAssets });
}, [assetsResponse, assetsSwrError]);
🤖 Prompt for AI Agents
In `@src/components/forms/CreateBookingForm.tsx` around lines 556 - 570, The
effect currently dispatches SET_ASSET_ERROR whenever assetsSwrError is truthy
which clears assets even if assetsResponse (stale data) exists; change the logic
in the useEffect that reads assetsSwrError and assetsResponse so you only
dispatchAsset({ type: "SET_ASSET_ERROR", error: true }) when assetsSwrError is
truthy AND assetsResponse is falsy/empty, otherwise preserve existing assets and
only dispatch SET_ASSETS from the normalizedAssets mapping; update the useEffect
that references assetsSwrError, assetsResponse, dispatchAsset, and AssetOption
to implement this conditional check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant