@terpjs/react-core 0.5.9 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ import { useRef } from "react";
2
+
3
+ import { useResource } from "./useResource";
4
+
5
+ /** One async record: the loaded row (or `null`) plus loading/error state and a reload. */
6
+ export interface RecordResource<T> {
7
+ /** The loaded record, or `null` until the load resolves / when it does not exist. */
8
+ item: T | null;
9
+ /** True while the initial load or a reload is in flight. */
10
+ loading: boolean;
11
+ /** The last error message, or `null` when the most recent load succeeded. */
12
+ error: string | null;
13
+ /**
14
+ * The last caught failure itself, or `null` — typically the `ApiError` thrown by
15
+ * `unwrap`, whose stable `code` lets `useErrorMessage` map it to client-owned copy.
16
+ */
17
+ cause?: unknown;
18
+ /** Re-run the get query. */
19
+ reload: () => Promise<void>;
20
+ /** Run any record-specific mutation (patch, delete, action), surface failures, then reload. */
21
+ mutate: (operation: () => Promise<void>) => Promise<void>;
22
+ }
23
+
24
+ /** How a detail screen fetches its one record — typically a typed contract-client call. */
25
+ export interface RecordSource<T> {
26
+ /**
27
+ * Fetch the record. Return `null` for "does not exist and that is a normal state"
28
+ * (compose with `unwrapOptional`); let `unwrap` throw when absence is an error.
29
+ */
30
+ get: () => Promise<T | null>;
31
+ }
32
+
33
+ /**
34
+ * The singleton counterpart of {@link useResource}: the one record a detail screen
35
+ * shows, instead of a collection. Before it existed every detail page spelled the
36
+ * record as a one-element list — `list: async () => [unwrap(await client.GET(...))]`
37
+ * then `items[0]` — a wart this hook deletes wherever detail pages exist.
38
+ *
39
+ * Same contract as `useResource`: `source` may be rebuilt each render (read through a
40
+ * ref), and `deps` (e.g. the route param the query closes over) reloads on in-place
41
+ * navigation between records. Implemented over `useResource` so the two state
42
+ * machines cannot drift.
43
+ */
44
+ export function useRecord<T>(
45
+ source: RecordSource<T>,
46
+ deps: readonly unknown[] = [],
47
+ ): RecordResource<T> {
48
+ const sourceRef = useRef(source);
49
+ sourceRef.current = source;
50
+
51
+ const resource = useResource<T | null>(
52
+ { list: async () => [await sourceRef.current.get()] },
53
+ deps,
54
+ );
55
+
56
+ return {
57
+ item: resource.items[0] ?? null,
58
+ loading: resource.loading,
59
+ error: resource.error,
60
+ cause: resource.cause,
61
+ reload: resource.reload,
62
+ mutate: resource.mutate,
63
+ };
64
+ }