@sanity/sdk-react 2.8.0 → 2.10.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.
- package/dist/index.d.ts +232 -47
- package/dist/index.js +468 -263
- package/dist/index.js.map +1 -1
- package/package.json +8 -10
- package/src/_exports/sdk-react.ts +5 -0
- package/src/components/SDKProvider.tsx +36 -8
- package/src/components/SanityApp.tsx +3 -2
- package/src/components/auth/AuthBoundary.tsx +8 -1
- package/src/components/auth/DashboardAccessRequest.tsx +37 -0
- package/src/components/auth/LoginError.test.tsx +191 -5
- package/src/components/auth/LoginError.tsx +100 -56
- package/src/components/errors/ChunkLoadError.test.tsx +59 -0
- package/src/components/errors/ChunkLoadError.tsx +56 -0
- package/src/components/errors/chunkReloadStorage.ts +57 -0
- package/src/context/ResourceProvider.test.tsx +7 -1
- package/src/context/ResourceProvider.tsx +11 -4
- package/src/context/ResourcesContext.tsx +7 -0
- package/src/context/SDKStudioContext.ts +6 -0
- package/src/context/SanityInstanceProvider.test.tsx +100 -0
- package/src/context/SanityInstanceProvider.tsx +71 -0
- package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
- package/src/hooks/dashboard/useDispatchIntent.test.ts +8 -6
- package/src/hooks/dashboard/useDispatchIntent.ts +6 -6
- package/src/hooks/dashboard/useWindowTitle.test.ts +213 -0
- package/src/hooks/dashboard/useWindowTitle.ts +112 -0
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +15 -15
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +13 -13
- package/src/hooks/document/useApplyDocumentActions.test.ts +113 -10
- package/src/hooks/document/useApplyDocumentActions.ts +99 -3
- package/src/hooks/document/useDocument.ts +22 -6
- package/src/hooks/document/useDocumentEvent.test.tsx +3 -3
- package/src/hooks/document/useDocumentEvent.ts +10 -3
- package/src/hooks/document/useDocumentPermissions.test.tsx +86 -2
- package/src/hooks/document/useDocumentPermissions.ts +22 -0
- package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -2
- package/src/hooks/document/useDocumentSyncStatus.ts +14 -5
- package/src/hooks/document/useEditDocument.ts +34 -8
- package/src/hooks/documents/useDocuments.ts +11 -6
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +131 -0
- package/src/hooks/helpers/useTrackHookUsage.ts +37 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +11 -8
- package/src/hooks/presence/usePresence.test.tsx +56 -9
- package/src/hooks/presence/usePresence.ts +25 -4
- package/src/hooks/preview/useDocumentPreview.test.tsx +84 -193
- package/src/hooks/preview/useDocumentPreview.tsx +40 -55
- package/src/hooks/projection/useDocumentProjection.ts +8 -6
- package/src/hooks/query/useQuery.ts +12 -9
- package/src/hooks/releases/useActiveReleases.ts +32 -13
- package/src/hooks/releases/usePerspective.ts +26 -14
- package/src/hooks/users/useUser.ts +2 -0
- package/src/hooks/users/useUsers.ts +2 -0
- package/src/context/SourcesContext.tsx +0 -7
- package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -85
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
type DocumentHandle,
|
|
3
|
+
PREVIEW_PROJECTION,
|
|
4
|
+
type PreviewQueryResult,
|
|
5
|
+
type PreviewValue,
|
|
6
|
+
transformProjectionToPreview,
|
|
7
|
+
} from '@sanity/sdk'
|
|
8
|
+
import {useMemo} from 'react'
|
|
4
9
|
|
|
5
10
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
11
|
+
import {
|
|
12
|
+
useNormalizedResourceOptions,
|
|
13
|
+
type WithResourceNameSupport,
|
|
14
|
+
} from '../helpers/useNormalizedResourceOptions'
|
|
15
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
16
|
+
import {useDocumentProjection} from '../projection/useDocumentProjection'
|
|
6
17
|
|
|
7
18
|
/**
|
|
8
19
|
* @public
|
|
9
20
|
* @category Types
|
|
10
21
|
*/
|
|
11
|
-
export interface useDocumentPreviewOptions extends DocumentHandle {
|
|
22
|
+
export interface useDocumentPreviewOptions extends WithResourceNameSupport<DocumentHandle> {
|
|
12
23
|
/**
|
|
13
24
|
* Optional ref object to track visibility. When provided, preview resolution
|
|
14
25
|
* only occurs when the referenced element is visible in the viewport.
|
|
@@ -21,7 +32,7 @@ export interface useDocumentPreviewOptions extends DocumentHandle {
|
|
|
21
32
|
* @category Types
|
|
22
33
|
*/
|
|
23
34
|
export interface useDocumentPreviewResults {
|
|
24
|
-
/** The results of inferring the document
|
|
35
|
+
/** The results of inferring the document's preview values */
|
|
25
36
|
data: PreviewValue
|
|
26
37
|
/** True when inferred preview values are being refreshed */
|
|
27
38
|
isPending: boolean
|
|
@@ -31,7 +42,7 @@ export interface useDocumentPreviewResults {
|
|
|
31
42
|
* @public
|
|
32
43
|
*
|
|
33
44
|
* Attempts to infer preview values of a document (specified via a `DocumentHandle`),
|
|
34
|
-
* including the document
|
|
45
|
+
* including the document's `title`, `subtitle`, `media`, and `status`. These values are live and will update in realtime.
|
|
35
46
|
* To reduce unnecessary network requests for resolving the preview values, an optional `ref` can be passed to the hook so that preview
|
|
36
47
|
* resolution will only occur if the `ref` is intersecting the current viewport.
|
|
37
48
|
*
|
|
@@ -40,9 +51,13 @@ export interface useDocumentPreviewResults {
|
|
|
40
51
|
* @remarks
|
|
41
52
|
* Values returned by this hook may not be as expected. It is currently unable to read preview values as defined in your schema;
|
|
42
53
|
* instead, it attempts to infer these preview values by checking against a basic set of potential fields on your document.
|
|
43
|
-
* We are anticipating being able to significantly improve this hook
|
|
54
|
+
* We are anticipating being able to significantly improve this hook's functionality and output in a future release.
|
|
44
55
|
* For now, we recommend using {@link useDocumentProjection} for rendering individual document fields (or projections of those fields).
|
|
45
56
|
*
|
|
57
|
+
* Internally, this hook is implemented as a specialized projection with post-processing logic.
|
|
58
|
+
* It uses a fixed GROQ projection to fetch common preview fields (title, subtitle, media) and
|
|
59
|
+
* transforms the results into the PreviewValue format.
|
|
60
|
+
*
|
|
46
61
|
* @category Documents
|
|
47
62
|
* @param options - The document handle for the document you want to infer preview values for, and an optional ref
|
|
48
63
|
* @returns The inferred values for the given document and a boolean to indicate whether the resolution is pending
|
|
@@ -84,56 +99,26 @@ export function useDocumentPreview({
|
|
|
84
99
|
...docHandle
|
|
85
100
|
}: useDocumentPreviewOptions): useDocumentPreviewResults {
|
|
86
101
|
const instance = useSanityInstance(docHandle)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Create subscribe function for useSyncExternalStore
|
|
90
|
-
const subscribe = useCallback(
|
|
91
|
-
(onStoreChanged: () => void) => {
|
|
92
|
-
const subscription = new Observable<boolean>((observer) => {
|
|
93
|
-
// For environments that don't have an intersection observer (e.g. server-side),
|
|
94
|
-
// we pass true to always subscribe since we can't detect visibility
|
|
95
|
-
if (typeof IntersectionObserver === 'undefined' || typeof HTMLElement === 'undefined') {
|
|
96
|
-
observer.next(true)
|
|
97
|
-
return
|
|
98
|
-
}
|
|
102
|
+
trackHookUsage(instance, 'useDocumentPreview')
|
|
103
|
+
const normalizedDocHandle = useNormalizedResourceOptions(docHandle)
|
|
99
104
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
} else {
|
|
107
|
-
// If no ref is provided or ref.current isn't an HTML element,
|
|
108
|
-
// pass true to always subscribe since we can't track visibility
|
|
109
|
-
observer.next(true)
|
|
110
|
-
}
|
|
111
|
-
return () => intersectionObserver.disconnect()
|
|
112
|
-
})
|
|
113
|
-
.pipe(
|
|
114
|
-
startWith(false),
|
|
115
|
-
distinctUntilChanged(),
|
|
116
|
-
switchMap((isVisible) =>
|
|
117
|
-
isVisible
|
|
118
|
-
? new Observable<void>((obs) => {
|
|
119
|
-
return stateSource.subscribe(() => obs.next())
|
|
120
|
-
})
|
|
121
|
-
: EMPTY,
|
|
122
|
-
),
|
|
123
|
-
)
|
|
124
|
-
.subscribe({next: onStoreChanged})
|
|
105
|
+
// Use the projection hook with the fixed preview projection
|
|
106
|
+
const projectionResult = useDocumentProjection<PreviewQueryResult>({
|
|
107
|
+
...normalizedDocHandle,
|
|
108
|
+
projection: PREVIEW_PROJECTION,
|
|
109
|
+
ref,
|
|
110
|
+
})
|
|
125
111
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
112
|
+
// Contract: useDocumentProjection suspends while data is null, so data is always available here.
|
|
113
|
+
// Keep this non-null assumption aligned with useDocumentPreviewResults.data.
|
|
114
|
+
const previewValue = useMemo(
|
|
115
|
+
() =>
|
|
116
|
+
transformProjectionToPreview(instance, projectionResult.data, normalizedDocHandle.resource),
|
|
117
|
+
[projectionResult.data, instance, normalizedDocHandle.resource],
|
|
129
118
|
)
|
|
130
119
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return currentState as useDocumentPreviewResults
|
|
136
|
-
}, [docHandle, instance, stateSource])
|
|
137
|
-
|
|
138
|
-
return useSyncExternalStore(subscribe, getSnapshot)
|
|
120
|
+
return {
|
|
121
|
+
data: previewValue,
|
|
122
|
+
isPending: projectionResult.isPending,
|
|
123
|
+
}
|
|
139
124
|
}
|
|
@@ -5,9 +5,10 @@ import {distinctUntilChanged, EMPTY, Observable, startWith, switchMap} from 'rxj
|
|
|
5
5
|
|
|
6
6
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
type
|
|
10
|
-
} from '../helpers/
|
|
8
|
+
useNormalizedResourceOptions,
|
|
9
|
+
type WithResourceNameSupport,
|
|
10
|
+
} from '../helpers/useNormalizedResourceOptions'
|
|
11
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @public
|
|
@@ -18,7 +19,7 @@ export interface useDocumentProjectionOptions<
|
|
|
18
19
|
TDocumentType extends string = string,
|
|
19
20
|
TDataset extends string = string,
|
|
20
21
|
TProjectId extends string = string,
|
|
21
|
-
> extends
|
|
22
|
+
> extends WithResourceNameSupport<DocumentHandle<TDocumentType, TDataset, TProjectId>> {
|
|
22
23
|
/** The GROQ projection string */
|
|
23
24
|
projection: TProjection
|
|
24
25
|
/** Optional parameters for the projection query */
|
|
@@ -181,14 +182,15 @@ export function useDocumentProjection<TData extends object>({
|
|
|
181
182
|
...docHandle
|
|
182
183
|
}: useDocumentProjectionOptions): useDocumentProjectionResults<TData> {
|
|
183
184
|
const instance = useSanityInstance(docHandle)
|
|
185
|
+
trackHookUsage(instance, 'useDocumentProjection')
|
|
184
186
|
|
|
185
187
|
// Normalize projection string to handle template literals with whitespace
|
|
186
188
|
// This ensures that the same projection content produces the same state source
|
|
187
189
|
// even if the string reference changes (e.g., from inline template literals)
|
|
188
190
|
const normalizedProjection = useMemo(() => projection.trim(), [projection])
|
|
189
191
|
|
|
190
|
-
// Normalize options: resolve
|
|
191
|
-
const normalizedDocHandle =
|
|
192
|
+
// Normalize options: resolve resourceName to resource and strip resourceName
|
|
193
|
+
const normalizedDocHandle = useNormalizedResourceOptions(docHandle)
|
|
192
194
|
|
|
193
195
|
// Memoize stateSource based on normalized projection and docHandle properties
|
|
194
196
|
// This prevents creating a new StateSource on every render when projection content is the same
|
|
@@ -10,18 +10,19 @@ import {useEffect, useMemo, useRef, useState, useSyncExternalStore, useTransitio
|
|
|
10
10
|
|
|
11
11
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
type
|
|
15
|
-
} from '../helpers/
|
|
13
|
+
useNormalizedResourceOptions,
|
|
14
|
+
type WithResourceNameSupport,
|
|
15
|
+
} from '../helpers/useNormalizedResourceOptions'
|
|
16
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
16
17
|
/**
|
|
17
|
-
* Hook options for useQuery, supporting both direct
|
|
18
|
+
* Hook options for useQuery, supporting both direct resource and resourceName.
|
|
18
19
|
* @beta
|
|
19
20
|
*/
|
|
20
21
|
type UseQueryOptions<
|
|
21
22
|
TQuery extends string = string,
|
|
22
23
|
TDataset extends string = string,
|
|
23
24
|
TProjectId extends string = string,
|
|
24
|
-
> =
|
|
25
|
+
> = WithResourceNameSupport<QueryOptions<TQuery, TDataset, TProjectId>>
|
|
25
26
|
|
|
26
27
|
// Overload 1: Inferred Type (using Typegen)
|
|
27
28
|
/**
|
|
@@ -121,7 +122,7 @@ export function useQuery<
|
|
|
121
122
|
* }
|
|
122
123
|
* ```
|
|
123
124
|
*/
|
|
124
|
-
export function useQuery<TData>(options:
|
|
125
|
+
export function useQuery<TData>(options: WithResourceNameSupport<QueryOptions>): {
|
|
125
126
|
/** The query result, cast to the provided type TData */
|
|
126
127
|
data: TData
|
|
127
128
|
/** True if another query is resolving in the background (suspense handles the initial loading state) */
|
|
@@ -146,15 +147,16 @@ export function useQuery<TData>(options: WithSourceNameSupport<QueryOptions>): {
|
|
|
146
147
|
*
|
|
147
148
|
* @category GROQ
|
|
148
149
|
*/
|
|
149
|
-
export function useQuery(options:
|
|
150
|
+
export function useQuery(options: WithResourceNameSupport<QueryOptions>): {
|
|
150
151
|
data: unknown
|
|
151
152
|
isPending: boolean
|
|
152
153
|
} {
|
|
153
154
|
// Implementation returns unknown, overloads define specifics
|
|
154
155
|
const instance = useSanityInstance(options)
|
|
156
|
+
trackHookUsage(instance, 'useQuery')
|
|
155
157
|
|
|
156
|
-
// Normalize options: resolve
|
|
157
|
-
const normalized =
|
|
158
|
+
// Normalize options: resolve resourceName to resource and strip resourceName
|
|
159
|
+
const normalized = useNormalizedResourceOptions(options)
|
|
158
160
|
|
|
159
161
|
// Use React's useTransition to avoid UI jank when queries change
|
|
160
162
|
const [isPending, startTransition] = useTransition()
|
|
@@ -202,6 +204,7 @@ export function useQuery(options: WithSourceNameSupport<QueryOptions>): {
|
|
|
202
204
|
const currentSignal = ref.current.signal
|
|
203
205
|
const deferred = parseQueryKey(deferredQueryKey)
|
|
204
206
|
|
|
207
|
+
// eslint-disable-next-line react-hooks/refs -- intentional during suspended render; see comment above.
|
|
205
208
|
throw resolveQuery(instance, {...deferred, signal: currentSignal})
|
|
206
209
|
}
|
|
207
210
|
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type DocumentResource,
|
|
2
3
|
getActiveReleasesState,
|
|
3
4
|
type ReleaseDocument,
|
|
5
|
+
type SanityConfig,
|
|
4
6
|
type SanityInstance,
|
|
5
7
|
type StateSource,
|
|
6
8
|
} from '@sanity/sdk'
|
|
7
9
|
import {filter, firstValueFrom} from 'rxjs'
|
|
8
10
|
|
|
9
11
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type UseActiveReleases = {
|
|
15
|
-
(): ReleaseDocument[]
|
|
16
|
-
}
|
|
12
|
+
import {
|
|
13
|
+
useNormalizedResourceOptions,
|
|
14
|
+
type WithResourceNameSupport,
|
|
15
|
+
} from '../helpers/useNormalizedResourceOptions'
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* @public
|
|
@@ -30,10 +29,30 @@ type UseActiveReleases = {
|
|
|
30
29
|
* const activeReleases = useActiveReleases()
|
|
31
30
|
* ```
|
|
32
31
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
type UseActiveReleases = {
|
|
33
|
+
(options?: WithResourceNameSupport<SanityConfig> | undefined): ReleaseDocument[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const useActiveReleasesValue: UseActiveReleases = createStateSourceHook({
|
|
37
|
+
getState: getActiveReleasesState as (
|
|
38
|
+
instance: SanityInstance,
|
|
39
|
+
options?: {resource?: DocumentResource},
|
|
40
|
+
) => StateSource<ReleaseDocument[]>,
|
|
41
|
+
shouldSuspend: (instance: SanityInstance, options?: {resource?: DocumentResource}) =>
|
|
42
|
+
getActiveReleasesState(instance, options ?? {}).getCurrent() === undefined,
|
|
43
|
+
suspender: (instance: SanityInstance, options?: {resource?: DocumentResource}) =>
|
|
44
|
+
firstValueFrom(
|
|
45
|
+
getActiveReleasesState(instance, options ?? {}).observable.pipe(filter(Boolean)),
|
|
46
|
+
),
|
|
39
47
|
})
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @public
|
|
51
|
+
* @function
|
|
52
|
+
*/
|
|
53
|
+
export const useActiveReleases: UseActiveReleases = (
|
|
54
|
+
options: WithResourceNameSupport<{resource?: DocumentResource}> | undefined,
|
|
55
|
+
) => {
|
|
56
|
+
const normalizedOptions = useNormalizedResourceOptions(options ?? {})
|
|
57
|
+
return useActiveReleasesValue(normalizedOptions)
|
|
58
|
+
}
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
type DatasetHandle,
|
|
3
|
+
type DocumentResource,
|
|
3
4
|
getPerspectiveState,
|
|
4
|
-
type PerspectiveHandle,
|
|
5
5
|
type SanityInstance,
|
|
6
6
|
type StateSource,
|
|
7
7
|
} from '@sanity/sdk'
|
|
8
8
|
import {filter, firstValueFrom} from 'rxjs'
|
|
9
9
|
|
|
10
10
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
type UsePerspective = {
|
|
16
|
-
(perspectiveHandle: PerspectiveHandle): string | string[]
|
|
17
|
-
}
|
|
11
|
+
import {
|
|
12
|
+
useNormalizedResourceOptions,
|
|
13
|
+
type WithResourceNameSupport,
|
|
14
|
+
} from '../helpers/useNormalizedResourceOptions'
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
17
|
* @public
|
|
@@ -38,13 +35,28 @@ type UsePerspective = {
|
|
|
38
35
|
*
|
|
39
36
|
* @returns The perspective for the given perspective handle.
|
|
40
37
|
*/
|
|
41
|
-
|
|
38
|
+
type UsePerspective = {
|
|
39
|
+
(perspectiveHandle: DatasetHandle): string | string[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const usePerspectiveValue: UsePerspective = createStateSourceHook({
|
|
42
43
|
getState: getPerspectiveState as (
|
|
43
44
|
instance: SanityInstance,
|
|
44
|
-
perspectiveHandle?:
|
|
45
|
+
perspectiveHandle?: {resource?: DocumentResource},
|
|
45
46
|
) => StateSource<string | string[]>,
|
|
46
|
-
shouldSuspend: (instance: SanityInstance, options:
|
|
47
|
+
shouldSuspend: (instance: SanityInstance, options: {resource?: DocumentResource}): boolean =>
|
|
47
48
|
getPerspectiveState(instance, options).getCurrent() === undefined,
|
|
48
|
-
suspender: (instance: SanityInstance, _options?:
|
|
49
|
-
firstValueFrom(
|
|
49
|
+
suspender: (instance: SanityInstance, _options?: {resource?: DocumentResource}) =>
|
|
50
|
+
firstValueFrom(getPerspectiveState(instance, _options ?? {}).observable.pipe(filter(Boolean))),
|
|
50
51
|
})
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @public
|
|
55
|
+
* @function
|
|
56
|
+
*/
|
|
57
|
+
export const usePerspective: UsePerspective = (
|
|
58
|
+
options: WithResourceNameSupport<DatasetHandle> | undefined,
|
|
59
|
+
) => {
|
|
60
|
+
const normalizedOptions = useNormalizedResourceOptions(options ?? {})
|
|
61
|
+
return usePerspectiveValue(normalizedOptions)
|
|
62
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import {useEffect, useMemo, useState, useSyncExternalStore, useTransition} from 'react'
|
|
10
10
|
|
|
11
11
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
12
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* @public
|
|
@@ -59,6 +60,7 @@ export interface UserResult {
|
|
|
59
60
|
*/
|
|
60
61
|
export function useUser(options: GetUserOptions): UserResult {
|
|
61
62
|
const instance = useSanityInstance(options)
|
|
63
|
+
trackHookUsage(instance, 'useUser')
|
|
62
64
|
// Use React's useTransition to avoid UI jank when user options change
|
|
63
65
|
const [isPending, startTransition] = useTransition()
|
|
64
66
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import {useCallback, useEffect, useMemo, useState, useSyncExternalStore, useTransition} from 'react'
|
|
11
11
|
|
|
12
12
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
13
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @public
|
|
@@ -69,6 +70,7 @@ export interface UsersResult {
|
|
|
69
70
|
*/
|
|
70
71
|
export function useUsers(options?: GetUsersOptions): UsersResult {
|
|
71
72
|
const instance = useSanityInstance(options)
|
|
73
|
+
trackHookUsage(instance, 'useUsers')
|
|
72
74
|
// Use React's useTransition to avoid UI jank when user options change
|
|
73
75
|
const [isPending, startTransition] = useTransition()
|
|
74
76
|
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import {type DocumentSource} from '@sanity/sdk'
|
|
2
|
-
import {useContext} from 'react'
|
|
3
|
-
|
|
4
|
-
import {SourcesContext} from '../../context/SourcesContext'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Adds React hook support (sourceName resolution) to core types.
|
|
8
|
-
* This wrapper allows hooks to accept `sourceName` as a convenience,
|
|
9
|
-
* which is then resolved to a `DocumentSource` at the React layer.
|
|
10
|
-
* For now, we are trying to avoid source name resolution in core --
|
|
11
|
-
* functions having sources explicitly passed will reduce complexity.
|
|
12
|
-
*
|
|
13
|
-
* @typeParam T - The core type to extend (must have optional `source` field)
|
|
14
|
-
* @beta
|
|
15
|
-
*/
|
|
16
|
-
export type WithSourceNameSupport<T extends {source?: DocumentSource}> = T & {
|
|
17
|
-
/**
|
|
18
|
-
* Optional name of a source to resolve from context.
|
|
19
|
-
* If provided, will be resolved to a `DocumentSource` via `SourcesContext`.
|
|
20
|
-
* @beta
|
|
21
|
-
*/
|
|
22
|
-
sourceName?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Normalizes hook options by resolving `sourceName` to a `DocumentSource`.
|
|
27
|
-
* This hook ensures that options passed to core layer functions only contain
|
|
28
|
-
* `source` (never `sourceName`), preventing duplicate cache keys and maintaining
|
|
29
|
-
* clean separation between React and core layers.
|
|
30
|
-
*
|
|
31
|
-
* @typeParam T - The options type (must include optional source field)
|
|
32
|
-
* @param options - Hook options that may include `sourceName` and/or `source`
|
|
33
|
-
* @returns Normalized options with `sourceName` removed and `source` resolved
|
|
34
|
-
*
|
|
35
|
-
* @remarks
|
|
36
|
-
* Resolution priority:
|
|
37
|
-
* 1. If `sourceName` is provided, resolves it via `SourcesContext` and uses that
|
|
38
|
-
* 2. Otherwise, uses the inline `source` if provided
|
|
39
|
-
* 3. If neither is provided, returns options without a source field
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```tsx
|
|
43
|
-
* function useQuery(options: WithSourceNameSupport<QueryOptions>) {
|
|
44
|
-
* const instance = useSanityInstance(options)
|
|
45
|
-
* const normalized = useNormalizedOptions(options)
|
|
46
|
-
* // normalized now has source but never sourceName
|
|
47
|
-
* const queryKey = getQueryKey(normalized)
|
|
48
|
-
* }
|
|
49
|
-
* ```
|
|
50
|
-
*
|
|
51
|
-
* @beta
|
|
52
|
-
*/
|
|
53
|
-
export function useNormalizedSourceOptions<
|
|
54
|
-
T extends {source?: DocumentSource; sourceName?: string},
|
|
55
|
-
>(options: T): Omit<T, 'sourceName'> {
|
|
56
|
-
const {sourceName, ...rest} = options
|
|
57
|
-
if (sourceName && Object.hasOwn(options, 'source')) {
|
|
58
|
-
throw new Error(
|
|
59
|
-
`Source name ${JSON.stringify(sourceName)} and source ${JSON.stringify(options.source)} cannot be used together.`,
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Resolve sourceName to source via context
|
|
64
|
-
const sources = useContext(SourcesContext)
|
|
65
|
-
let resolvedSource: DocumentSource | undefined
|
|
66
|
-
|
|
67
|
-
if (options.source) {
|
|
68
|
-
resolvedSource = options.source
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (sourceName && !Object.hasOwn(sources, sourceName)) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`There's no source named ${JSON.stringify(sourceName)} in context. Please use <SourceProvider>.`,
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (sourceName && sources[sourceName]) {
|
|
78
|
-
resolvedSource = sources[sourceName]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
...rest,
|
|
83
|
-
source: resolvedSource,
|
|
84
|
-
}
|
|
85
|
-
}
|