# Availability data refreshed from server on every calendar view load

- **Pattern:** `ab-000517` (`booking-calendar-availability.calendar-display.data-freshness`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000517
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000517)

## Why it matters

Serving stale availability data is a direct cause of double-bookings. When a customer books slot A, that change must be visible to every other client on the next calendar load — not after a session refresh or browser restart. Client-side caches without TTLs violate CWE-362 (concurrent resource access) and ISO 25010 functional correctness: two users see the same slot as open and both confirm. The downstream effect is a support ticket, a refund, and a damaged reputation. Booking platforms live and die by schedule integrity; a single double-booking erodes trust faster than any competitor can.

## Severity rationale

Critical because stale availability displayed to the user is the direct mechanical cause of double-bookings, which are unrecoverable without manual intervention and refunds.

## Remediation

Add `month` and `year` (or equivalent time-period identifiers) to the `useEffect` dependency array in your calendar component so a fresh fetch fires on every navigation, not just on mount. Include a cache-buster parameter or set `Cache-Control: no-cache` on the server endpoint.

```tsx
useEffect(() => {
  fetch(`/api/availability?month=${month}&year=${year}&t=${Date.now()}`)
    .then(r => r.json())
    .then(setSlots)
}, [month, year]) // re-runs on every period change
```

On the API route in `src/app/api/availability/route.ts`, set the response header:

```ts
return new Response(JSON.stringify(slots), {
  headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate' },
})
```

## Detection

- **ID:** `data-freshness`
- **Severity:** `critical`
- **What to look for:** Find the calendar component and trace its data loading logic. Count all calendar view components (month view, week view, day view). For each, check if there's an API call (`fetch`, `axios`, `useSWR`, `useQuery`, or HTTP client) that fires when the calendar mounts or when the user navigates to a different month/week. Look for caching mechanisms — if the code caches availability data without a time-based expiry or cache-buster, flag it. Check if the availability endpoint has a `Cache-Control` header set to `no-cache` or `max-age=0`. Examine files matching `src/components/*calendar*`, `src/components/*Calendar*`, `src/app/*booking*`, `src/app/*calendar*`, `src/app/api/availability*`.
- **Pass criteria:** Count all calendar view mount/navigation paths. 100% of calendar view loads (mount or navigation to a different time period) must trigger a fresh API call to the availability endpoint. No stale cached data may be displayed. The availability fetch must include either a cache-buster parameter (e.g., `Date.now()`) or proper `Cache-Control: no-cache` headers. Report: "X of Y calendar view paths fetch fresh data from the server."
- **Fail criteria:** Availability data is cached indefinitely on the client side, or the API is called only once per session and data is reused even after bookings may have changed.
- **Do NOT pass when:** A `useEffect` or data-fetching hook exists but its dependency array is empty (`[]`) — this means it fires only on mount, not on navigation between time periods. Also do NOT pass when `staleTime: Infinity` or equivalent infinite caching is configured in React Query / SWR.
- **Skip (N/A) when:** Never — calendar availability must always be current.
- **Detail on fail:** Example: `"Calendar mounts with a static list of slots fetched once at app init. Subsequent calendar views reuse the same data without refreshing. Booked slots remain marked available."`
- **Cross-reference:** Check `buffer-enforcement` — stale data combined with missing buffer validation causes double-bookings.
- **Cross-reference:** Check `live-availability-update` — if availability is not refetched on view load, it is also likely stale after booking.
- **Cross-reference:** Check `performance-load-time` — fresh fetches should not block first paint; use Suspense or skeleton.
- **Remediation:** Fetch availability fresh on every calendar load or view change:

  ```tsx
  import { useEffect, useState } from 'react'

  export function Calendar({ month, year }) {
    const [slots, setSlots] = useState([])

    useEffect(() => {
      // Fetch whenever month/year changes
      const fetchSlots = async () => {
        const response = await fetch(
          `/api/availability?month=${month}&year=${year}&cache-bust=${Date.now()}`
        )
        const data = await response.json()
        setSlots(data)
      }
      fetchSlots()
    }, [month, year])

    return (
      <div>
        {slots.map((slot) => (
          <div key={slot.id}>{slot.time}</div>
        ))}
      </div>
    )
  }
  ```

  Ensure the server endpoint has proper cache headers:
  ```ts
  res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
  ```

## External references

- cwe CWE-362
- iso-25010 functional-correctness

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000517
