@sanity/sdk-react 2.8.0 → 3.0.0-rc.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/README.md +125 -63
- package/dist/index.d.ts +381 -571
- package/dist/index.js +435 -366
- package/dist/index.js.map +1 -1
- package/package.json +7 -9
- package/src/_exports/index.ts +4 -0
- package/src/_exports/sdk-react.ts +16 -0
- package/src/components/SDKProvider.test.tsx +23 -58
- package/src/components/SDKProvider.tsx +38 -30
- package/src/components/SanityApp.test.tsx +12 -68
- package/src/components/SanityApp.tsx +88 -65
- package/src/components/auth/AuthBoundary.test.tsx +8 -26
- package/src/components/auth/LoginError.tsx +5 -5
- package/src/config/handles.ts +53 -0
- package/src/context/ComlinkTokenRefresh.test.tsx +27 -10
- package/src/context/DefaultResourceContext.ts +10 -0
- package/src/context/PerspectiveContext.ts +12 -0
- package/src/context/ResourceProvider.test.tsx +99 -19
- package/src/context/ResourceProvider.tsx +103 -37
- package/src/context/ResourcesContext.tsx +7 -0
- package/src/context/SDKStudioContext.test.tsx +33 -28
- package/src/context/SDKStudioContext.ts +6 -0
- package/src/context/renderSanityApp.test.tsx +49 -151
- package/src/context/renderSanityApp.tsx +8 -12
- package/src/hooks/agent/agentActions.test.tsx +1 -1
- package/src/hooks/agent/agentActions.ts +56 -19
- package/src/hooks/auth/useDashboardOrganizationId.test.tsx +8 -2
- package/src/hooks/auth/useVerifyOrgProjects.test.tsx +32 -8
- package/src/hooks/client/useClient.test.tsx +4 -1
- package/src/hooks/client/useClient.ts +0 -1
- package/src/hooks/context/useDefaultResource.test.tsx +25 -0
- package/src/hooks/context/useDefaultResource.ts +30 -0
- package/src/hooks/context/useSanityInstance.test.tsx +2 -140
- package/src/hooks/context/useSanityInstance.ts +9 -53
- package/src/hooks/dashboard/useDispatchIntent.test.ts +24 -15
- package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
- package/src/hooks/dashboard/useManageFavorite.test.tsx +34 -94
- package/src/hooks/dashboard/useManageFavorite.ts +16 -10
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +7 -5
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +6 -2
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +2 -0
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +17 -38
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +12 -19
- package/src/hooks/datasets/useDatasets.test.ts +8 -22
- package/src/hooks/datasets/useDatasets.ts +8 -16
- package/src/hooks/document/useApplyDocumentActions.test.ts +98 -52
- package/src/hooks/document/useApplyDocumentActions.ts +35 -37
- package/src/hooks/document/useDocument.test.tsx +8 -37
- package/src/hooks/document/useDocument.ts +78 -129
- package/src/hooks/document/useDocumentEvent.test.tsx +7 -19
- package/src/hooks/document/useDocumentEvent.ts +21 -19
- package/src/hooks/document/useDocumentPermissions.test.tsx +75 -84
- package/src/hooks/document/useDocumentPermissions.ts +41 -28
- package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -3
- package/src/hooks/document/useDocumentSyncStatus.ts +19 -14
- package/src/hooks/document/useEditDocument.test.tsx +28 -70
- package/src/hooks/document/useEditDocument.ts +29 -149
- package/src/hooks/documents/useDocuments.test.tsx +44 -64
- package/src/hooks/documents/useDocuments.ts +19 -25
- package/src/hooks/helpers/createCallbackHook.test.tsx +19 -13
- package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
- package/src/hooks/helpers/createStateSourceHook.tsx +2 -4
- package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +65 -0
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +127 -0
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +27 -34
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +19 -20
- package/src/hooks/presence/usePresence.test.tsx +71 -9
- package/src/hooks/presence/usePresence.ts +28 -3
- package/src/hooks/preview/useDocumentPreview.test.tsx +85 -193
- package/src/hooks/preview/useDocumentPreview.tsx +42 -62
- package/src/hooks/projection/useDocumentProjection.test.tsx +9 -37
- package/src/hooks/projection/useDocumentProjection.ts +9 -82
- package/src/hooks/projects/useProject.test.ts +1 -2
- package/src/hooks/projects/useProject.ts +7 -8
- package/src/hooks/query/useQuery.test.tsx +5 -6
- package/src/hooks/query/useQuery.ts +12 -91
- package/src/hooks/releases/useActiveReleases.test.tsx +2 -2
- package/src/hooks/releases/useActiveReleases.ts +25 -13
- package/src/hooks/releases/usePerspective.test.tsx +9 -17
- package/src/hooks/releases/usePerspective.ts +29 -18
- package/src/hooks/users/useUser.test.tsx +9 -3
- package/src/hooks/users/useUser.ts +1 -1
- package/src/hooks/users/useUsers.test.tsx +5 -2
- package/src/hooks/users/useUsers.ts +1 -1
- package/src/context/SourcesContext.tsx +0 -7
- package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -85
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {type SanityProjectionResult} from 'groq'
|
|
1
|
+
import {getProjectionState, resolveProjection} from '@sanity/sdk'
|
|
3
2
|
import {useCallback, useMemo, useSyncExternalStore} from 'react'
|
|
4
3
|
import {distinctUntilChanged, EMPTY, Observable, startWith, switchMap} from 'rxjs'
|
|
5
4
|
|
|
5
|
+
import {type DocumentHandle} from '../../config/handles'
|
|
6
6
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
7
|
-
import {
|
|
8
|
-
useNormalizedSourceOptions,
|
|
9
|
-
type WithSourceNameSupport,
|
|
10
|
-
} from '../helpers/useNormalizedSourceOptions'
|
|
7
|
+
import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
10
|
* @public
|
|
@@ -18,7 +15,7 @@ export interface useDocumentProjectionOptions<
|
|
|
18
15
|
TDocumentType extends string = string,
|
|
19
16
|
TDataset extends string = string,
|
|
20
17
|
TProjectId extends string = string,
|
|
21
|
-
> extends
|
|
18
|
+
> extends DocumentHandle<TDocumentType, TDataset, TProjectId> {
|
|
22
19
|
/** The GROQ projection string */
|
|
23
20
|
projection: TProjection
|
|
24
21
|
/** Optional parameters for the projection query */
|
|
@@ -48,83 +45,13 @@ export interface useDocumentProjectionResults<TData> {
|
|
|
48
45
|
*
|
|
49
46
|
* @category Documents
|
|
50
47
|
* @remarks
|
|
51
|
-
* This hook
|
|
52
|
-
* - Using Typegen: Infers the return type based on the `documentType`, `dataset`, `projectId`, and `projection`.
|
|
53
|
-
* - Using explicit type parameter: Allows specifying a custom return type `TData`.
|
|
48
|
+
* This hook allows specifying an explicit type parameter `TData` for the projected result.
|
|
54
49
|
*
|
|
55
50
|
* @param options - An object containing the `DocumentHandle` properties (`documentId`, `documentType`, etc.), the `projection` string, optional `params`, and an optional `ref`.
|
|
56
51
|
* @returns An object containing the projection results (`data`) and a boolean indicating whether the resolution is pending (`isPending`). Note: Suspense handles initial loading states; `data` being `undefined` after initial loading means the document doesn't exist or the projection yielded no result.
|
|
57
52
|
*/
|
|
58
53
|
|
|
59
|
-
// Overload 1:
|
|
60
|
-
/**
|
|
61
|
-
* @public
|
|
62
|
-
* Fetch a projection, relying on Typegen for the return type based on the handle and projection.
|
|
63
|
-
*
|
|
64
|
-
* @category Documents
|
|
65
|
-
* @param options - Options including the document handle properties (`documentId`, `documentType`, etc.) and the `projection`.
|
|
66
|
-
* @returns The projected data, typed based on Typegen.
|
|
67
|
-
*
|
|
68
|
-
* @example Using Typegen for a book preview
|
|
69
|
-
* ```tsx
|
|
70
|
-
* // ProjectionComponent.tsx
|
|
71
|
-
* import {useDocumentProjection, type DocumentHandle} from '@sanity/sdk-react'
|
|
72
|
-
* import {useRef} from 'react'
|
|
73
|
-
* import {defineProjection} from 'groq'
|
|
74
|
-
*
|
|
75
|
-
* // Define props using DocumentHandle with the specific document type
|
|
76
|
-
* type ProjectionComponentProps = {
|
|
77
|
-
* doc: DocumentHandle<'book'> // Typegen knows 'book'
|
|
78
|
-
* }
|
|
79
|
-
*
|
|
80
|
-
* // This is required for typegen to generate the correct return type
|
|
81
|
-
* const myProjection = defineProjection(`{
|
|
82
|
-
* title,
|
|
83
|
-
* 'coverImage': cover.asset->url,
|
|
84
|
-
* 'authors': array::join(authors[]->{'name': firstName + ' ' + lastName}.name, ', ')
|
|
85
|
-
* }`)
|
|
86
|
-
*
|
|
87
|
-
* export default function ProjectionComponent({ doc }: ProjectionComponentProps) {
|
|
88
|
-
* const ref = useRef(null) // Optional ref to track viewport intersection for lazy loading
|
|
89
|
-
*
|
|
90
|
-
* // Spread the doc handle into the options
|
|
91
|
-
* // Typegen infers the return type based on 'book' and the projection
|
|
92
|
-
* const { data } = useDocumentProjection({
|
|
93
|
-
* ...doc, // Pass the handle properties
|
|
94
|
-
* ref,
|
|
95
|
-
* projection: myProjection,
|
|
96
|
-
* })
|
|
97
|
-
*
|
|
98
|
-
* // Suspense handles initial load, check for data existence after
|
|
99
|
-
* return (
|
|
100
|
-
* <article ref={ref}>
|
|
101
|
-
* <h2>{data.title ?? 'Untitled'}</h2>
|
|
102
|
-
* {data.coverImage && <img src={data.coverImage} alt={data.title} />}
|
|
103
|
-
* <p>{data.authors ?? 'Unknown authors'}</p>
|
|
104
|
-
* </article>
|
|
105
|
-
* )
|
|
106
|
-
* }
|
|
107
|
-
*
|
|
108
|
-
* // Usage:
|
|
109
|
-
* // import {createDocumentHandle} from '@sanity/sdk-react'
|
|
110
|
-
* // const myDocHandle = createDocumentHandle({ documentId: 'book123', documentType: 'book' })
|
|
111
|
-
* // <Suspense fallback='Loading preview...'>
|
|
112
|
-
* // <ProjectionComponent doc={myDocHandle} />
|
|
113
|
-
* // </Suspense>
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
export function useDocumentProjection<
|
|
117
|
-
TProjection extends string = string,
|
|
118
|
-
TDocumentType extends string = string,
|
|
119
|
-
TDataset extends string = string,
|
|
120
|
-
TProjectId extends string = string,
|
|
121
|
-
>(
|
|
122
|
-
options: useDocumentProjectionOptions<TProjection, TDocumentType, TDataset, TProjectId>,
|
|
123
|
-
): useDocumentProjectionResults<
|
|
124
|
-
SanityProjectionResult<TProjection, TDocumentType, `${TProjectId}.${TDataset}`>
|
|
125
|
-
>
|
|
126
|
-
|
|
127
|
-
// Overload 2: Explicit type provided
|
|
54
|
+
// Overload 1: Explicit type provided
|
|
128
55
|
/**
|
|
129
56
|
* @public
|
|
130
57
|
* Fetch a projection with an explicitly defined return type `TData`.
|
|
@@ -180,15 +107,15 @@ export function useDocumentProjection<TData extends object>({
|
|
|
180
107
|
projection,
|
|
181
108
|
...docHandle
|
|
182
109
|
}: useDocumentProjectionOptions): useDocumentProjectionResults<TData> {
|
|
183
|
-
const instance = useSanityInstance(
|
|
110
|
+
const instance = useSanityInstance()
|
|
184
111
|
|
|
185
112
|
// Normalize projection string to handle template literals with whitespace
|
|
186
113
|
// This ensures that the same projection content produces the same state source
|
|
187
114
|
// even if the string reference changes (e.g., from inline template literals)
|
|
188
115
|
const normalizedProjection = useMemo(() => projection.trim(), [projection])
|
|
189
116
|
|
|
190
|
-
// Normalize options: resolve
|
|
191
|
-
const normalizedDocHandle =
|
|
117
|
+
// Normalize options: resolve resourceName to resource and strip resourceName
|
|
118
|
+
const normalizedDocHandle = useNormalizedResourceOptions(docHandle)
|
|
192
119
|
|
|
193
120
|
// Memoize stateSource based on normalized projection and docHandle properties
|
|
194
121
|
// This prevents creating a new StateSource on every render when projection content is the same
|
|
@@ -40,8 +40,7 @@ describe('useProject', () => {
|
|
|
40
40
|
expect.objectContaining({
|
|
41
41
|
getState: expect.any(Function),
|
|
42
42
|
shouldSuspend: expect.any(Function),
|
|
43
|
-
suspender: expect.any(Function),
|
|
44
|
-
getConfig: expect.any(Function), // Actual function reference doesn't matter here
|
|
43
|
+
suspender: expect.any(Function),
|
|
45
44
|
}),
|
|
46
45
|
)
|
|
47
46
|
})
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
type SanityProject,
|
|
7
7
|
type StateSource,
|
|
8
8
|
} from '@sanity/sdk'
|
|
9
|
-
import {identity} from 'rxjs'
|
|
10
9
|
|
|
11
10
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
12
11
|
|
|
@@ -16,12 +15,12 @@ type UseProject = {
|
|
|
16
15
|
* Returns metadata for a given project
|
|
17
16
|
*
|
|
18
17
|
* @category Projects
|
|
19
|
-
* @param
|
|
18
|
+
* @param projectHandle - An optional project handle identifying which project to retrieve metadata for
|
|
20
19
|
* @returns The metadata for the project
|
|
21
20
|
* @example
|
|
22
21
|
* ```tsx
|
|
23
22
|
* function ProjectMetadata({ projectId }: { projectId: string }) {
|
|
24
|
-
* const project = useProject(projectId)
|
|
23
|
+
* const project = useProject({ projectId })
|
|
25
24
|
*
|
|
26
25
|
* return (
|
|
27
26
|
* <figure style={{ backgroundColor: project.metadata.color || 'lavender'}}>
|
|
@@ -44,8 +43,8 @@ export const useProject: UseProject = createStateSourceHook({
|
|
|
44
43
|
instance: SanityInstance,
|
|
45
44
|
projectHandle?: ProjectHandle,
|
|
46
45
|
) => StateSource<SanityProject>,
|
|
47
|
-
shouldSuspend: (instance, projectHandle) =>
|
|
48
|
-
getProjectState(instance, projectHandle).getCurrent() === undefined,
|
|
49
|
-
suspender:
|
|
50
|
-
|
|
51
|
-
})
|
|
46
|
+
shouldSuspend: (instance: SanityInstance, projectHandle?: ProjectHandle) =>
|
|
47
|
+
getProjectState(instance, projectHandle as ProjectHandle).getCurrent() === undefined,
|
|
48
|
+
suspender: (instance: SanityInstance, projectHandle?: ProjectHandle) =>
|
|
49
|
+
resolveProject(instance, projectHandle as ProjectHandle),
|
|
50
|
+
}) as UseProject
|
|
@@ -33,7 +33,7 @@ describe('useQuery', () => {
|
|
|
33
33
|
} as StateSource<unknown>)
|
|
34
34
|
|
|
35
35
|
function TestComponent() {
|
|
36
|
-
const {data, isPending} = useQuery({query: 'test query'})
|
|
36
|
+
const {data, isPending} = useQuery<string>({query: 'test query'})
|
|
37
37
|
return (
|
|
38
38
|
<div data-testid="output">
|
|
39
39
|
{data} - {isPending ? 'pending' : 'not pending'}
|
|
@@ -42,7 +42,7 @@ describe('useQuery', () => {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
render(
|
|
45
|
-
<ResourceProvider projectId
|
|
45
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={<p>Loading...</p>}>
|
|
46
46
|
<TestComponent />
|
|
47
47
|
</ResourceProvider>,
|
|
48
48
|
)
|
|
@@ -82,14 +82,13 @@ describe('useQuery', () => {
|
|
|
82
82
|
)
|
|
83
83
|
|
|
84
84
|
function TestComponent() {
|
|
85
|
-
const {data} = useQuery({query: 'test query'})
|
|
85
|
+
const {data} = useQuery<string>({query: 'test query'})
|
|
86
86
|
return <div data-testid="output">{data}</div>
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
render(
|
|
90
90
|
<ResourceProvider
|
|
91
|
-
projectId
|
|
92
|
-
dataset="d"
|
|
91
|
+
resource={{projectId: 'p', dataset: 'd'}}
|
|
93
92
|
fallback={<div data-testid="fallback">Loading...</div>}
|
|
94
93
|
>
|
|
95
94
|
<TestComponent />
|
|
@@ -164,7 +163,7 @@ describe('useQuery', () => {
|
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
render(
|
|
167
|
-
<ResourceProvider projectId
|
|
166
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={<p>Loading...</p>}>
|
|
168
167
|
<WrapperComponent />
|
|
169
168
|
</ResourceProvider>,
|
|
170
169
|
)
|
|
@@ -5,99 +5,21 @@ import {
|
|
|
5
5
|
type QueryOptions,
|
|
6
6
|
resolveQuery,
|
|
7
7
|
} from '@sanity/sdk'
|
|
8
|
-
import {type SanityQueryResult} from 'groq'
|
|
9
8
|
import {useEffect, useMemo, useRef, useState, useSyncExternalStore, useTransition} from 'react'
|
|
10
9
|
|
|
10
|
+
import {type ResourceHandle} from '../../config/handles'
|
|
11
11
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
12
|
-
import {
|
|
13
|
-
useNormalizedSourceOptions,
|
|
14
|
-
type WithSourceNameSupport,
|
|
15
|
-
} from '../helpers/useNormalizedSourceOptions'
|
|
16
|
-
/**
|
|
17
|
-
* Hook options for useQuery, supporting both direct source and sourceName.
|
|
18
|
-
* @beta
|
|
19
|
-
*/
|
|
20
|
-
type UseQueryOptions<
|
|
21
|
-
TQuery extends string = string,
|
|
22
|
-
TDataset extends string = string,
|
|
23
|
-
TProjectId extends string = string,
|
|
24
|
-
> = WithSourceNameSupport<QueryOptions<TQuery, TDataset, TProjectId>>
|
|
12
|
+
import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
|
|
25
13
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* @public
|
|
29
|
-
* Executes a GROQ query, inferring the result type from the query string and options.
|
|
30
|
-
* Leverages Sanity Typegen if configured for enhanced type safety.
|
|
31
|
-
*
|
|
32
|
-
* @param options - Configuration for the query, including `query`, optional `params`, `projectId`, `dataset`, etc.
|
|
33
|
-
* @returns An object containing `data` (typed based on the query) and `isPending` (for transitions).
|
|
34
|
-
*
|
|
35
|
-
* @example Basic usage (Inferred Type)
|
|
36
|
-
* ```tsx
|
|
37
|
-
* import {useQuery} from '@sanity/sdk-react'
|
|
38
|
-
* import {defineQuery} from 'groq'
|
|
39
|
-
*
|
|
40
|
-
* const myQuery = defineQuery(`*[_type == "movie"]{_id, title}`)
|
|
41
|
-
*
|
|
42
|
-
* function MovieList() {
|
|
43
|
-
* // Typegen infers the return type for data
|
|
44
|
-
* const {data} = useQuery({ query: myQuery })
|
|
45
|
-
*
|
|
46
|
-
* return (
|
|
47
|
-
* <div>
|
|
48
|
-
* <h2>Movies</h2>
|
|
49
|
-
* <ul>
|
|
50
|
-
* {data.map(movie => <li key={movie._id}>{movie.title}</li>)}
|
|
51
|
-
* </ul>
|
|
52
|
-
* </div>
|
|
53
|
-
* )
|
|
54
|
-
* }
|
|
55
|
-
* // Suspense boundary should wrap <MovieList /> for initial load
|
|
56
|
-
* ```
|
|
57
|
-
*
|
|
58
|
-
* @example Using parameters (Inferred Type)
|
|
59
|
-
* ```tsx
|
|
60
|
-
* import {useQuery} from '@sanity/sdk-react'
|
|
61
|
-
* import {defineQuery} from 'groq'
|
|
62
|
-
*
|
|
63
|
-
* const myQuery = defineQuery(`*[_type == "movie" && _id == $id][0]`)
|
|
64
|
-
*
|
|
65
|
-
* function MovieDetails({movieId}: {movieId: string}) {
|
|
66
|
-
* // Typegen infers the return type based on query and params
|
|
67
|
-
* const {data, isPending} = useQuery({
|
|
68
|
-
* query: myQuery,
|
|
69
|
-
* params: { id: movieId }
|
|
70
|
-
* })
|
|
71
|
-
*
|
|
72
|
-
* return (
|
|
73
|
-
* // utilize `isPending` to signal to users that new data is coming in
|
|
74
|
-
* // (e.g. the `movieId` changed and we're loading in the new one)
|
|
75
|
-
* <div style={{ opacity: isPending ? 0.5 : 1 }}>
|
|
76
|
-
* {data ? <h1>{data.title}</h1> : <p>Movie not found</p>}
|
|
77
|
-
* </div>
|
|
78
|
-
* )
|
|
79
|
-
* }
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
export function useQuery<
|
|
83
|
-
TQuery extends string = string,
|
|
84
|
-
TDataset extends string = string,
|
|
85
|
-
TProjectId extends string = string,
|
|
86
|
-
>(
|
|
87
|
-
options: UseQueryOptions<TQuery, TDataset, TProjectId>,
|
|
88
|
-
): {
|
|
89
|
-
/** The query result, typed based on the GROQ query string */
|
|
90
|
-
data: SanityQueryResult<TQuery, `${TProjectId}.${TDataset}`>
|
|
91
|
-
/** True if a query transition is in progress */
|
|
92
|
-
isPending: boolean
|
|
93
|
-
}
|
|
14
|
+
/** Options for useQuery: QueryOptions with resource made optional (resolved from context) */
|
|
15
|
+
type ReactQueryOptions = Omit<QueryOptions, 'resource' | 'resourceName'> & ResourceHandle
|
|
94
16
|
|
|
95
|
-
// Overload
|
|
17
|
+
// Overload 1: Explicit Type Provided
|
|
96
18
|
/**
|
|
97
19
|
* @public
|
|
98
20
|
* Executes a GROQ query with an explicitly provided result type `TData`.
|
|
99
21
|
*
|
|
100
|
-
* @param options - Configuration for the query, including `query`, optional `params`, `
|
|
22
|
+
* @param options - Configuration for the query, including `query`, optional `params`, `resource`, etc.
|
|
101
23
|
* @returns An object containing `data` (cast to `TData`) and `isPending` (indicates whether a query resolution is pending; note that Suspense handles initial loading states). *
|
|
102
24
|
* @example Manually typed query result
|
|
103
25
|
* ```tsx
|
|
@@ -121,7 +43,7 @@ export function useQuery<
|
|
|
121
43
|
* }
|
|
122
44
|
* ```
|
|
123
45
|
*/
|
|
124
|
-
export function useQuery<TData>(options:
|
|
46
|
+
export function useQuery<TData>(options: ReactQueryOptions): {
|
|
125
47
|
/** The query result, cast to the provided type TData */
|
|
126
48
|
data: TData
|
|
127
49
|
/** True if another query is resolving in the background (suspense handles the initial loading state) */
|
|
@@ -141,20 +63,19 @@ export function useQuery<TData>(options: WithSourceNameSupport<QueryOptions>): {
|
|
|
141
63
|
* - Subscribes to changes, providing real-time updates.
|
|
142
64
|
* - Integrates with React Suspense for handling initial loading states.
|
|
143
65
|
* - Uses React Transitions for managing loading states during query/parameter changes (indicated by `isPending`).
|
|
144
|
-
* - Supports type inference based on the GROQ query when using Sanity Typegen.
|
|
145
66
|
* - Allows specifying an explicit return type `TData` for the query result.
|
|
146
67
|
*
|
|
147
68
|
* @category GROQ
|
|
148
69
|
*/
|
|
149
|
-
export function useQuery(options:
|
|
70
|
+
export function useQuery(options: ReactQueryOptions): {
|
|
150
71
|
data: unknown
|
|
151
72
|
isPending: boolean
|
|
152
73
|
} {
|
|
153
74
|
// Implementation returns unknown, overloads define specifics
|
|
154
|
-
const instance = useSanityInstance(
|
|
75
|
+
const instance = useSanityInstance()
|
|
155
76
|
|
|
156
|
-
// Normalize options: resolve
|
|
157
|
-
const normalized =
|
|
77
|
+
// Normalize options: resolve resourceName to resource and strip resourceName
|
|
78
|
+
const normalized = useNormalizedResourceOptions(options)
|
|
158
79
|
|
|
159
80
|
// Use React's useTransition to avoid UI jank when queries change
|
|
160
81
|
const [isPending, startTransition] = useTransition()
|
|
@@ -207,6 +128,6 @@ export function useQuery(options: WithSourceNameSupport<QueryOptions>): {
|
|
|
207
128
|
|
|
208
129
|
// Subscribe to updates and get the current data
|
|
209
130
|
// useSyncExternalStore ensures the component re-renders when the data changes
|
|
210
|
-
const data = useSyncExternalStore(subscribe, getCurrent) as
|
|
131
|
+
const data = useSyncExternalStore(subscribe, getCurrent) as unknown
|
|
211
132
|
return useMemo(() => ({data, isPending}), [data, isPending])
|
|
212
133
|
}
|
|
@@ -43,7 +43,7 @@ describe('useActiveReleases', () => {
|
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
wrapper: ({children}) => (
|
|
46
|
-
<ResourceProvider projectId
|
|
46
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={<p>Loading...</p>}>
|
|
47
47
|
{children}
|
|
48
48
|
</ResourceProvider>
|
|
49
49
|
),
|
|
@@ -75,7 +75,7 @@ describe('useActiveReleases', () => {
|
|
|
75
75
|
|
|
76
76
|
const {result} = renderHook(() => useActiveReleases(), {
|
|
77
77
|
wrapper: ({children}) => (
|
|
78
|
-
<ResourceProvider projectId
|
|
78
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'd'}} fallback={<p>Loading...</p>}>
|
|
79
79
|
{children}
|
|
80
80
|
</ResourceProvider>
|
|
81
81
|
),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type DocumentResource,
|
|
2
3
|
getActiveReleasesState,
|
|
3
4
|
type ReleaseDocument,
|
|
4
5
|
type SanityInstance,
|
|
@@ -6,14 +7,9 @@ import {
|
|
|
6
7
|
} from '@sanity/sdk'
|
|
7
8
|
import {filter, firstValueFrom} from 'rxjs'
|
|
8
9
|
|
|
10
|
+
import {type ResourceHandle} from '../../config/handles'
|
|
9
11
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
type UseActiveReleases = {
|
|
15
|
-
(): ReleaseDocument[]
|
|
16
|
-
}
|
|
12
|
+
import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
|
|
17
13
|
|
|
18
14
|
/**
|
|
19
15
|
* @public
|
|
@@ -30,10 +26,26 @@ type UseActiveReleases = {
|
|
|
30
26
|
* const activeReleases = useActiveReleases()
|
|
31
27
|
* ```
|
|
32
28
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
type UseActiveReleases = {
|
|
30
|
+
(options?: ResourceHandle | undefined): ReleaseDocument[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const useActiveReleasesValue = createStateSourceHook({
|
|
34
|
+
getState: getActiveReleasesState as (
|
|
35
|
+
instance: SanityInstance,
|
|
36
|
+
options: {resource: DocumentResource},
|
|
37
|
+
) => StateSource<ReleaseDocument[]>,
|
|
38
|
+
shouldSuspend: (instance: SanityInstance, options: {resource: DocumentResource}) =>
|
|
39
|
+
getActiveReleasesState(instance, options).getCurrent() === undefined,
|
|
40
|
+
suspender: (instance: SanityInstance, options: {resource: DocumentResource}) =>
|
|
41
|
+
firstValueFrom(getActiveReleasesState(instance, options).observable.pipe(filter(Boolean))),
|
|
39
42
|
})
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @public
|
|
46
|
+
* @function
|
|
47
|
+
*/
|
|
48
|
+
export const useActiveReleases: UseActiveReleases = (options: ResourceHandle | undefined) => {
|
|
49
|
+
const normalizedOptions = useNormalizedResourceOptions(options ?? {})
|
|
50
|
+
return useActiveReleasesValue(normalizedOptions)
|
|
51
|
+
}
|
|
@@ -5,11 +5,10 @@ import {
|
|
|
5
5
|
type PerspectiveHandle,
|
|
6
6
|
type ReleaseDocument,
|
|
7
7
|
} from '@sanity/sdk'
|
|
8
|
-
import {renderHook} from '@testing-library/react'
|
|
9
8
|
import {BehaviorSubject} from 'rxjs'
|
|
10
9
|
import {describe, expect, it, vi} from 'vitest'
|
|
11
10
|
|
|
12
|
-
import {
|
|
11
|
+
import {renderHook} from '../../../test/test-utils'
|
|
13
12
|
import {usePerspective} from './usePerspective'
|
|
14
13
|
|
|
15
14
|
// Mock the SDK functions
|
|
@@ -68,18 +67,13 @@ describe('usePerspective', () => {
|
|
|
68
67
|
vi.mocked(getPerspectiveState).mockReturnValue(mockStateSource)
|
|
69
68
|
vi.mocked(getActiveReleasesState).mockReturnValue(mockReleasesStateSource)
|
|
70
69
|
|
|
71
|
-
const {result} = renderHook(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
wrapper: ({children}) => <ResourceProvider fallback={null}>{children}</ResourceProvider>,
|
|
81
|
-
},
|
|
82
|
-
)
|
|
70
|
+
const {result} = renderHook(() => {
|
|
71
|
+
try {
|
|
72
|
+
return usePerspective(perspectiveHandle)
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return e
|
|
75
|
+
}
|
|
76
|
+
})
|
|
83
77
|
|
|
84
78
|
// Verify that the hook threw a promise (suspended)
|
|
85
79
|
expect(result.current).toBeInstanceOf(Promise)
|
|
@@ -104,9 +98,7 @@ describe('usePerspective', () => {
|
|
|
104
98
|
|
|
105
99
|
vi.mocked(getPerspectiveState).mockReturnValue(mockStateSource)
|
|
106
100
|
|
|
107
|
-
const {result} = renderHook(() => usePerspective(perspectiveHandle)
|
|
108
|
-
wrapper: ({children}) => <ResourceProvider fallback={null}>{children}</ResourceProvider>,
|
|
109
|
-
})
|
|
101
|
+
const {result} = renderHook(() => usePerspective(perspectiveHandle))
|
|
110
102
|
|
|
111
103
|
// Verify that the hook returned the perspective without suspending
|
|
112
104
|
expect(result.current).toEqual(mockPerspective)
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
type DocumentResource,
|
|
3
3
|
getPerspectiveState,
|
|
4
|
-
type PerspectiveHandle,
|
|
5
4
|
type SanityInstance,
|
|
6
5
|
type StateSource,
|
|
7
6
|
} from '@sanity/sdk'
|
|
8
7
|
import {filter, firstValueFrom} from 'rxjs'
|
|
9
8
|
|
|
9
|
+
import {type ResourceHandle} from '../../config/handles'
|
|
10
10
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @public
|
|
14
|
-
*/
|
|
15
|
-
type UsePerspective = {
|
|
16
|
-
(perspectiveHandle: PerspectiveHandle): string | string[]
|
|
17
|
-
}
|
|
11
|
+
import {useNormalizedResourceOptions} from '../helpers/useNormalizedResourceOptions'
|
|
18
12
|
|
|
19
13
|
/**
|
|
20
14
|
* @public
|
|
@@ -29,22 +23,39 @@ type UsePerspective = {
|
|
|
29
23
|
* @example
|
|
30
24
|
* ```tsx
|
|
31
25
|
* import {usePerspective, useQuery} from '@sanity/sdk-react'
|
|
32
|
-
|
|
33
|
-
* const perspective = usePerspective({
|
|
34
|
-
*
|
|
35
|
-
*
|
|
26
|
+
*
|
|
27
|
+
* const perspective = usePerspective({
|
|
28
|
+
* perspective: 'rxg1346',
|
|
29
|
+
* resource: {projectId: 'abc123', dataset: 'production'},
|
|
30
|
+
* })
|
|
31
|
+
* const {data} = useQuery<Movie[]>({
|
|
32
|
+
* query: '*[_type == "movie"]',
|
|
33
|
+
* perspective,
|
|
36
34
|
* })
|
|
37
35
|
* ```
|
|
38
36
|
*
|
|
39
37
|
* @returns The perspective for the given perspective handle.
|
|
40
38
|
*/
|
|
41
|
-
|
|
39
|
+
type UsePerspective = {
|
|
40
|
+
(perspectiveHandle?: ResourceHandle): string | string[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const usePerspectiveValue = createStateSourceHook({
|
|
42
44
|
getState: getPerspectiveState as (
|
|
43
45
|
instance: SanityInstance,
|
|
44
|
-
perspectiveHandle?:
|
|
46
|
+
perspectiveHandle: {resource: DocumentResource; perspective?: unknown},
|
|
45
47
|
) => StateSource<string | string[]>,
|
|
46
|
-
shouldSuspend: (instance: SanityInstance, options:
|
|
48
|
+
shouldSuspend: (instance: SanityInstance, options: {resource: DocumentResource}): boolean =>
|
|
47
49
|
getPerspectiveState(instance, options).getCurrent() === undefined,
|
|
48
|
-
suspender: (instance: SanityInstance, _options?:
|
|
49
|
-
firstValueFrom(
|
|
50
|
+
suspender: (instance: SanityInstance, _options?: {resource: DocumentResource}) =>
|
|
51
|
+
firstValueFrom(getPerspectiveState(instance, _options).observable.pipe(filter(Boolean))),
|
|
50
52
|
})
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @public
|
|
56
|
+
* @function
|
|
57
|
+
*/
|
|
58
|
+
export const usePerspective: UsePerspective = (options: ResourceHandle | undefined) => {
|
|
59
|
+
const normalizedOptions = useNormalizedResourceOptions(options ?? {})
|
|
60
|
+
return usePerspectiveValue(normalizedOptions)
|
|
61
|
+
}
|
|
@@ -88,7 +88,7 @@ describe('useUser', () => {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
render(
|
|
91
|
-
<ResourceProvider projectId
|
|
91
|
+
<ResourceProvider resource={{projectId: 'p', dataset: 'production'}} fallback={null}>
|
|
92
92
|
<TestComponent />
|
|
93
93
|
</ResourceProvider>,
|
|
94
94
|
)
|
|
@@ -340,7 +340,10 @@ describe('useUser', () => {
|
|
|
340
340
|
}
|
|
341
341
|
|
|
342
342
|
render(
|
|
343
|
-
<ResourceProvider
|
|
343
|
+
<ResourceProvider
|
|
344
|
+
resource={{projectId: 'test-project', dataset: 'production'}}
|
|
345
|
+
fallback={null}
|
|
346
|
+
>
|
|
344
347
|
<TestComponent />
|
|
345
348
|
</ResourceProvider>,
|
|
346
349
|
)
|
|
@@ -387,7 +390,10 @@ describe('useUser', () => {
|
|
|
387
390
|
}
|
|
388
391
|
|
|
389
392
|
render(
|
|
390
|
-
<ResourceProvider
|
|
393
|
+
<ResourceProvider
|
|
394
|
+
resource={{projectId: 'test-project', dataset: 'production'}}
|
|
395
|
+
fallback={null}
|
|
396
|
+
>
|
|
391
397
|
<WrapperComponent />
|
|
392
398
|
</ResourceProvider>,
|
|
393
399
|
)
|
|
@@ -58,7 +58,7 @@ export interface UserResult {
|
|
|
58
58
|
* ```
|
|
59
59
|
*/
|
|
60
60
|
export function useUser(options: GetUserOptions): UserResult {
|
|
61
|
-
const instance = useSanityInstance(
|
|
61
|
+
const instance = useSanityInstance()
|
|
62
62
|
// Use React's useTransition to avoid UI jank when user options change
|
|
63
63
|
const [isPending, startTransition] = useTransition()
|
|
64
64
|
|
|
@@ -313,7 +313,10 @@ describe('useUsers', () => {
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
render(
|
|
316
|
-
<ResourceProvider
|
|
316
|
+
<ResourceProvider
|
|
317
|
+
resource={{projectId: 'p', dataset: 'production'}}
|
|
318
|
+
fallback={<div data-testid="fallback">Loading...</div>}
|
|
319
|
+
>
|
|
317
320
|
<TestComponent />
|
|
318
321
|
</ResourceProvider>,
|
|
319
322
|
)
|
|
@@ -327,7 +330,7 @@ describe('useUsers', () => {
|
|
|
327
330
|
|
|
328
331
|
// Verify that loadMoreUsers was called with the correct arguments
|
|
329
332
|
expect(loadMoreUsers).toHaveBeenCalledWith(
|
|
330
|
-
expect.objectContaining({
|
|
333
|
+
expect.objectContaining({instanceId: expect.any(String)}),
|
|
331
334
|
{
|
|
332
335
|
resourceType: 'organization',
|
|
333
336
|
organizationId: 'test-org',
|
|
@@ -68,7 +68,7 @@ export interface UsersResult {
|
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
70
|
export function useUsers(options?: GetUsersOptions): UsersResult {
|
|
71
|
-
const instance = useSanityInstance(
|
|
71
|
+
const instance = useSanityInstance()
|
|
72
72
|
// Use React's useTransition to avoid UI jank when user options change
|
|
73
73
|
const [isPending, startTransition] = useTransition()
|
|
74
74
|
|