@nifrajs/web-react 0.1.0-alpha.1
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.
- package/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/await.d.ts +29 -0
- package/dist/await.d.ts.map +1 -0
- package/dist/await.js +50 -0
- package/dist/await.js.map +1 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +39 -0
- package/dist/client.js.map +1 -0
- package/dist/compose.d.ts +9 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +16 -0
- package/dist/compose.js.map +1 -0
- package/dist/content.d.ts +18 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +14 -0
- package/dist/content.js.map +1 -0
- package/dist/error.d.ts +8 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +32 -0
- package/dist/error.js.map +1 -0
- package/dist/fetcher.d.ts +23 -0
- package/dist/fetcher.d.ts.map +1 -0
- package/dist/fetcher.js +45 -0
- package/dist/fetcher.js.map +1 -0
- package/dist/i18n.d.ts +20 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +25 -0
- package/dist/i18n.js.map +1 -0
- package/dist/image.d.ts +25 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +21 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/island.d.ts +27 -0
- package/dist/island.d.ts.map +1 -0
- package/dist/island.js +23 -0
- package/dist/island.js.map +1 -0
- package/dist/query.d.ts +17 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +58 -0
- package/dist/query.js.map +1 -0
- package/package.json +101 -0
- package/src/await.ts +75 -0
- package/src/client.ts +42 -0
- package/src/compose.ts +17 -0
- package/src/content.ts +27 -0
- package/src/error.ts +33 -0
- package/src/fetcher.ts +66 -0
- package/src/i18n.ts +36 -0
- package/src/image.ts +43 -0
- package/src/index.ts +30 -0
- package/src/island.ts +39 -0
- package/src/query.ts +75 -0
package/src/query.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createQueryClient,
|
|
3
|
+
type QueryClient,
|
|
4
|
+
type QueryHandle,
|
|
5
|
+
type QueryState,
|
|
6
|
+
} from "@nifrajs/web"
|
|
7
|
+
/**
|
|
8
|
+
* `@nifrajs/web-react/query` — React bindings for the keyed query-cache. `useQuery(key, fn)` subscribes a
|
|
9
|
+
* component to a query (via `useSyncExternalStore`) and fetches on mount / key change; `useQueryClient`
|
|
10
|
+
* exposes `invalidateQueries` for after a mutation. Imports only `react` + `@nifrajs/web` (never
|
|
11
|
+
* `react-dom/*`), so route components use it on the server *and* client. No JSX (the package builds
|
|
12
|
+
* with plain `tsc`).
|
|
13
|
+
*
|
|
14
|
+
* The query client is created lazily **client-side only** (the `typeof window` guard) — on the server
|
|
15
|
+
* there is none, so `useQuery` renders the idle/pending state (queries are client-first; loaders are
|
|
16
|
+
* the SSR data source). The first client render uses the same idle server-snapshot, so no mismatch.
|
|
17
|
+
*/
|
|
18
|
+
import { useEffect, useSyncExternalStore } from "react"
|
|
19
|
+
|
|
20
|
+
let client: QueryClient | undefined
|
|
21
|
+
function getClient(): QueryClient | undefined {
|
|
22
|
+
if (typeof window === "undefined") return undefined // SSR → no client (queries render idle)
|
|
23
|
+
if (client === undefined) client = createQueryClient({ now: () => Date.now() })
|
|
24
|
+
return client
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Access the query client to imperatively `invalidateQueries(keyOrPrefix)` (e.g. after a mutation). */
|
|
28
|
+
export function useQueryClient(): Pick<QueryClient, "invalidateQueries"> {
|
|
29
|
+
return getClient() ?? { invalidateQueries: () => {} }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Stable idle snapshot for the server / pre-fetch render (stable ref → no loop, no hydration mismatch).
|
|
33
|
+
const IDLE: QueryState<never> = Object.freeze({
|
|
34
|
+
status: "pending",
|
|
35
|
+
data: undefined,
|
|
36
|
+
error: undefined,
|
|
37
|
+
isFetching: false,
|
|
38
|
+
updatedAt: Number.NEGATIVE_INFINITY,
|
|
39
|
+
})
|
|
40
|
+
const idleSnapshot = (): QueryState<never> => IDLE
|
|
41
|
+
const noopSubscribe = (): (() => void) => () => {}
|
|
42
|
+
const noopAsync = async (): Promise<never> => {
|
|
43
|
+
throw new Error("[nifra/web-react] useQuery.refetch called with no query client (server?)")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A query's reactive {@link QueryState} plus `isPending` + `refetch`. */
|
|
47
|
+
export interface UseQueryResult<T> extends QueryState<T> {
|
|
48
|
+
/** `status === "pending"` — no data yet (initial load). */
|
|
49
|
+
readonly isPending: boolean
|
|
50
|
+
/** Force a refetch (ignores `staleTime`). */
|
|
51
|
+
readonly refetch: () => Promise<T>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Subscribe to the keyed query for `key`, fetched via `fn`. Returns `{ status, data, error, isFetching,
|
|
56
|
+
* updatedAt, isPending, refetch }`. Concurrent `useQuery`s with the same key share one cache entry +
|
|
57
|
+
* one in-flight fetch (dedup). Refetches on mount and when the key changes; SSR-idle.
|
|
58
|
+
*/
|
|
59
|
+
export function useQuery<T>(key: unknown, fn: () => Promise<T>): UseQueryResult<T> {
|
|
60
|
+
const handle: QueryHandle<T> | undefined = getClient()?.query<T>(key, fn)
|
|
61
|
+
const state = useSyncExternalStore<QueryState<T>>(
|
|
62
|
+
handle?.subscribe ?? noopSubscribe,
|
|
63
|
+
handle ? handle.snapshot : idleSnapshot,
|
|
64
|
+
idleSnapshot,
|
|
65
|
+
)
|
|
66
|
+
// Fetch on mount + whenever the key changes (the handle is stable per key, so it changes with the key).
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (handle !== undefined) handle.fetch().catch(() => {})
|
|
69
|
+
}, [handle])
|
|
70
|
+
return {
|
|
71
|
+
...state,
|
|
72
|
+
isPending: state.status === "pending",
|
|
73
|
+
refetch: handle ? handle.refetch : (noopAsync as () => Promise<T>),
|
|
74
|
+
}
|
|
75
|
+
}
|