Ctrl K
ComunidadGuías…

src/components/dives/dive-detail-context.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"use client";

import { createContext, useContext, type ReactNode } from "react";
import type { Dive } from "@/lib/api/dives";
import type { Trip } from "@/lib/api/trips";
import type { Course } from "@/lib/api/courses";
import type { Contact } from "@/lib/api/contacts";
import type { Person } from "@/lib/api/people";

export interface DiveDetailValue {
  /**
   * Never null. The provider is the `(detail)` layout, which renders a skeleton
   * or a not-found state instead of its children until it holds a dive - so
   * anything below it is looking at a real one, and the dive being *replaced*
   * during a step is still a real one.
   */
  dive: Dive;
  /** A neighbouring dive is on its way in; `dive` is the one being stepped away from. */
  isLoading: boolean;
  /** The dive's trip, training course and contact, or null where it has none or the lookup failed. */
  trip: Trip | null;
  course: Course | null;
  contact: Contact | null;
  /**
   * The diver's people by uuid, for the names behind `dive.people`. Empty until
   * the read lands, and a person it does not hold simply has no row.
   */
  people: Readonly<Record<string, Person>>;
  /** Re-reads the dive without touching `isLoading` - see `useResource`. */
  refreshDive: () => void;
}

// No default value. A default would let a consumer render outside the layout
// against a dive that doesn't exist, and the only honest default for `dive` is
// null - which is exactly the case the layout exists to keep out of this tree.
const DiveDetailContext = createContext<DiveDetailValue | null>(null);

/**
 * Carries the dive from the route-group layout that fetches it down to the page
 * that renders it.
 *
 * The split exists because the App Router keys `[id]` on the param, so the page
 * is destroyed and rebuilt on every step of the prev/next pager. The layout
 * sits above that segment and survives, which is what lets the outgoing dive
 * stay on screen while the next one loads - and what keeps the pager's own
 * `<a>` alive under the focus that is on it. See "`dives/(detail)/layout.tsx`
 * owns the dive fetch, so a step keeps the page mounted" in DECISIONS.md.
 */
export function DiveDetailProvider({
  value,
  children,
}: {
  value: DiveDetailValue;
  children: ReactNode;
}) {
  return (
    <DiveDetailContext.Provider value={value}>
      {children}
    </DiveDetailContext.Provider>
  );
}

/** The dive the `/dives/[id]` layout has loaded, for the page rendered inside it. */
export function useDiveDetail(): DiveDetailValue {
  const value = useContext(DiveDetailContext);
  if (!value) {
    throw new Error("useDiveDetail must be used inside the dive detail layout");
  }
  return value;
}