@sanity/sdk-react 3.0.0-rc.0 → 3.0.0-rc.2

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.
Files changed (168) hide show
  1. package/README.md +103 -155
  2. package/dist/index.d.ts +2488 -2019
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +4435 -1529
  5. package/dist/index.js.map +1 -1
  6. package/package.json +55 -59
  7. package/src/_exports/index.ts +2 -4
  8. package/src/_exports/sdk-react.ts +30 -18
  9. package/src/components/SDKProvider.test.tsx +173 -32
  10. package/src/components/SDKProvider.tsx +87 -28
  11. package/src/components/SanityApp.test.tsx +68 -12
  12. package/src/components/SanityApp.tsx +93 -87
  13. package/src/components/auth/AuthBoundary.recovery.test.tsx +86 -0
  14. package/src/components/auth/AuthBoundary.test.tsx +7 -7
  15. package/src/components/auth/AuthBoundary.tsx +29 -6
  16. package/src/components/auth/DashboardAccessRequest.tsx +37 -0
  17. package/src/components/auth/LoginError.test.tsx +191 -5
  18. package/src/components/auth/LoginError.tsx +104 -61
  19. package/src/components/errors/ChunkLoadError.test.tsx +59 -0
  20. package/src/components/errors/ChunkLoadError.tsx +56 -0
  21. package/src/components/errors/CorsErrorComponent.tsx +2 -2
  22. package/src/components/errors/chunkReloadStorage.ts +57 -0
  23. package/src/config/handles.ts +29 -27
  24. package/src/constants.ts +5 -0
  25. package/src/context/ComlinkTokenRefresh.test.tsx +10 -27
  26. package/src/context/ComlinkTokenRefresh.tsx +3 -5
  27. package/src/context/DefaultResourceContext.ts +3 -3
  28. package/src/context/OrganizationResourcesProvider.test.tsx +189 -0
  29. package/src/context/OrganizationResourcesProvider.tsx +111 -0
  30. package/src/context/PerspectiveContext.ts +3 -3
  31. package/src/context/ProjectContext.ts +15 -0
  32. package/src/context/ResourceProvider.test.tsx +93 -90
  33. package/src/context/ResourceProvider.tsx +62 -93
  34. package/src/context/SDKStudioContext.test.tsx +28 -33
  35. package/src/context/SanityInstanceProvider.test.tsx +100 -0
  36. package/src/context/SanityInstanceProvider.tsx +71 -0
  37. package/src/context/WorkbenchTokenRefresh.test.tsx +106 -0
  38. package/src/context/WorkbenchTokenRefresh.tsx +61 -0
  39. package/src/context/renderSanityApp.test.tsx +151 -49
  40. package/src/context/renderSanityApp.tsx +12 -8
  41. package/src/context/workbenchToken.ts +63 -0
  42. package/src/hooks/access/useCheckPermissions.test-d.ts +15 -0
  43. package/src/hooks/access/useCheckPermissions.test.tsx +53 -0
  44. package/src/hooks/access/useCheckPermissions.ts +24 -0
  45. package/src/hooks/agent/agentActions.test.tsx +2 -2
  46. package/src/hooks/agent/agentActions.ts +52 -72
  47. package/src/hooks/agent/useAgentResourceContext.ts +1 -1
  48. package/src/hooks/applications/useApplication.test-d.ts +30 -0
  49. package/src/hooks/applications/useApplication.ts +22 -0
  50. package/src/hooks/applications/useApplications.test-d.ts +31 -0
  51. package/src/hooks/applications/useApplications.ts +25 -0
  52. package/src/hooks/applications/useDeleteApplication.test-d.ts +12 -0
  53. package/src/hooks/applications/useDeleteApplication.ts +11 -0
  54. package/src/hooks/applications/useUpdateApplication.test-d.ts +12 -0
  55. package/src/hooks/applications/useUpdateApplication.ts +11 -0
  56. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +2 -8
  57. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +8 -32
  58. package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
  59. package/src/hooks/client/useClient.test.tsx +1 -4
  60. package/src/hooks/client/useClient.ts +1 -0
  61. package/src/hooks/comlink/useFrameConnection.test.tsx +3 -2
  62. package/src/hooks/comlink/useFrameConnection.ts +1 -1
  63. package/src/hooks/comlink/useWindowConnection.test.tsx +4 -3
  64. package/src/hooks/comlink/useWindowConnection.ts +2 -3
  65. package/src/hooks/comments/useCommentActions.test.tsx +150 -0
  66. package/src/hooks/comments/useCommentActions.ts +109 -0
  67. package/src/hooks/comments/useCommentList.ts +79 -0
  68. package/src/hooks/comments/useCommentThreads.test.tsx +107 -0
  69. package/src/hooks/comments/useCommentThreads.ts +73 -0
  70. package/src/hooks/comments/useComments.test.tsx +242 -0
  71. package/src/hooks/comments/useComments.ts +59 -0
  72. package/src/hooks/context/useResource.test.tsx +32 -0
  73. package/src/hooks/context/useResource.ts +24 -0
  74. package/src/hooks/context/useSanityInstance.test.tsx +18 -1
  75. package/src/hooks/context/useSanityInstance.ts +6 -6
  76. package/src/hooks/dashboard/useManageFavorite.test.tsx +110 -46
  77. package/src/hooks/dashboard/useManageFavorite.ts +11 -17
  78. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +5 -7
  79. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -6
  80. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +0 -2
  81. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -2
  82. package/src/hooks/dashboard/useWindowTitle.test.ts +244 -0
  83. package/src/hooks/dashboard/useWindowTitle.ts +114 -0
  84. package/src/hooks/datasets/useDatasets.test.tsx +116 -0
  85. package/src/hooks/datasets/useDatasets.ts +33 -13
  86. package/src/hooks/document/useApplyDocumentActions.test.tsx +48 -0
  87. package/src/hooks/document/useApplyDocumentActions.ts +66 -48
  88. package/src/hooks/document/useCreateDocument.test.tsx +83 -0
  89. package/src/hooks/document/useCreateDocument.ts +117 -0
  90. package/src/hooks/document/useDocument.test.tsx +37 -8
  91. package/src/hooks/document/useDocument.ts +131 -66
  92. package/src/hooks/document/useDocumentEvent.test.tsx +16 -4
  93. package/src/hooks/document/useDocumentEvent.ts +16 -10
  94. package/src/hooks/document/useDocumentPermissions.test.tsx +8 -7
  95. package/src/hooks/document/useDocumentPermissions.ts +71 -39
  96. package/src/hooks/document/useDocumentSyncStatus.test.ts +1 -0
  97. package/src/hooks/document/useDocumentSyncStatus.ts +13 -9
  98. package/src/hooks/document/useEditDocument.test.tsx +21 -14
  99. package/src/hooks/document/useEditDocument.ts +163 -17
  100. package/src/hooks/documents/useDocuments.test.tsx +64 -44
  101. package/src/hooks/documents/useDocuments.ts +31 -20
  102. package/src/hooks/helpers/createCallbackHook.test.tsx +13 -19
  103. package/src/hooks/helpers/createFetcherHook.test.tsx +180 -0
  104. package/src/hooks/helpers/createFetcherHook.ts +69 -0
  105. package/src/hooks/helpers/createMutationHook.test.tsx +125 -0
  106. package/src/hooks/helpers/createMutationHook.tsx +93 -0
  107. package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
  108. package/src/hooks/helpers/createStateSourceHook.tsx +2 -1
  109. package/src/hooks/{document/useApplyDocumentActions.test.ts → helpers/useApplyActions.test.tsx} +92 -53
  110. package/src/hooks/helpers/useApplyActions.ts +68 -0
  111. package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +226 -0
  112. package/src/hooks/helpers/useNormalizedResourceOptions.ts +79 -48
  113. package/src/hooks/helpers/useResolvedProjectId.test.tsx +59 -0
  114. package/src/hooks/helpers/useResolvedProjectId.ts +35 -0
  115. package/src/hooks/helpers/useTrackHookUsage.ts +37 -0
  116. package/src/hooks/installations/useInstallation.test-d.ts +19 -0
  117. package/src/hooks/installations/useInstallation.ts +22 -0
  118. package/src/hooks/installations/useInstallations.test-d.ts +26 -0
  119. package/src/hooks/installations/useInstallations.ts +25 -0
  120. package/src/hooks/organizations/useOrganization.test-d.ts +60 -0
  121. package/src/hooks/organizations/useOrganization.test.ts +63 -0
  122. package/src/hooks/organizations/useOrganization.ts +34 -0
  123. package/src/hooks/organizations/useOrganizations.test-d.ts +68 -0
  124. package/src/hooks/organizations/useOrganizations.test.ts +63 -0
  125. package/src/hooks/organizations/useOrganizations.ts +39 -0
  126. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +34 -27
  127. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +35 -22
  128. package/src/hooks/presence/usePresence.test.tsx +0 -15
  129. package/src/hooks/presence/usePresence.ts +22 -20
  130. package/src/hooks/presence/usePresenceForDocument.test.tsx +141 -0
  131. package/src/hooks/presence/usePresenceForDocument.ts +104 -0
  132. package/src/hooks/presence/useReportPresence.test.tsx +202 -0
  133. package/src/hooks/presence/useReportPresence.ts +170 -0
  134. package/src/hooks/preview/useDocumentPreview.test.tsx +0 -1
  135. package/src/hooks/preview/useDocumentPreview.tsx +12 -14
  136. package/src/hooks/projection/useDocumentProjection.test.tsx +78 -9
  137. package/src/hooks/projection/useDocumentProjection.ts +81 -2
  138. package/src/hooks/projects/useProject.test-d.ts +56 -0
  139. package/src/hooks/projects/useProject.test.tsx +120 -0
  140. package/src/hooks/projects/useProject.ts +42 -43
  141. package/src/hooks/projects/useProjects.test-d.ts +60 -0
  142. package/src/hooks/projects/useProjects.test.ts +45 -98
  143. package/src/hooks/projects/useProjects.ts +17 -32
  144. package/src/hooks/query/useQuery.test.tsx +6 -5
  145. package/src/hooks/query/useQuery.ts +94 -17
  146. package/src/hooks/releases/useActiveReleases.test.tsx +13 -7
  147. package/src/hooks/releases/useActiveReleases.ts +28 -24
  148. package/src/hooks/releases/useAllReleases.test.tsx +93 -0
  149. package/src/hooks/releases/useAllReleases.ts +62 -0
  150. package/src/hooks/releases/useApplyReleaseActions.test.tsx +66 -0
  151. package/src/hooks/releases/useApplyReleaseActions.ts +82 -0
  152. package/src/hooks/releases/usePerspective.test.tsx +18 -10
  153. package/src/hooks/releases/usePerspective.ts +13 -17
  154. package/src/hooks/users/useUser.test.tsx +3 -9
  155. package/src/hooks/users/useUser.ts +4 -8
  156. package/src/hooks/users/useUsers.test.tsx +103 -7
  157. package/src/hooks/users/useUsers.ts +38 -5
  158. package/src/utils/resolveOrgResources.test.ts +111 -0
  159. package/src/utils/resolveOrgResources.ts +69 -0
  160. package/src/hooks/context/useDefaultResource.test.tsx +0 -25
  161. package/src/hooks/context/useDefaultResource.ts +0 -30
  162. package/src/hooks/dashboard/useDispatchIntent.test.ts +0 -254
  163. package/src/hooks/dashboard/useDispatchIntent.ts +0 -157
  164. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +0 -107
  165. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +0 -39
  166. package/src/hooks/datasets/useDatasets.test.ts +0 -66
  167. package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +0 -65
  168. package/src/hooks/projects/useProject.test.ts +0 -79
@@ -1,13 +1,10 @@
1
- import {type ActionsResult, applyDocumentActions, type DocumentAction} from '@sanity/sdk'
2
- import {useContext} from 'react'
1
+ import {type ActionsResult, type DocumentAction} from '@sanity/sdk'
2
+ import {type SanityDocument} from 'groq'
3
3
 
4
4
  import {type ResourceHandle} from '../../config/handles'
5
- import {ResourceContext} from '../../context/DefaultResourceContext'
6
- import {ResourcesContext} from '../../context/ResourcesContext'
7
- import {useSanityInstance} from '../context/useSanityInstance'
8
- import {normalizeResourceOptions} from '../helpers/useNormalizedResourceOptions'
5
+ import {useApplyActions} from '../helpers/useApplyActions'
9
6
  // this import is used in an `{@link useEditDocument}`
10
- // eslint-disable-next-line unused-imports/no-unused-imports, import/consistent-type-specifier-style
7
+ // eslint-disable-next-line import-x/consistent-type-specifier-style
11
8
  import type {useEditDocument} from './useEditDocument'
12
9
 
13
10
  /**
@@ -23,7 +20,7 @@ interface UseApplyDocumentActions {
23
20
  | DocumentAction<TDocumentType, TDataset, TProjectId>
24
21
  | DocumentAction<TDocumentType, TDataset, TProjectId>[],
25
22
  options?: ResourceHandle,
26
- ) => Promise<ActionsResult>
23
+ ) => Promise<ActionsResult<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>>>
27
24
  }
28
25
 
29
26
  /**
@@ -148,46 +145,67 @@ interface UseApplyDocumentActions {
148
145
  * return <button onClick={handleCreateArticle}>Create Article</button>
149
146
  * }
150
147
  * ```
148
+ *
149
+ * @example Create a new document in a release
150
+ * ```tsx
151
+ * import {
152
+ * createDocument,
153
+ * createDocumentHandle,
154
+ * useApplyDocumentActions
155
+ * } from '@sanity/sdk-react'
156
+ *
157
+ * function CreateArticleButton() {
158
+ * const apply = useApplyDocumentActions()
159
+ *
160
+ * const handleCreateArticle = () => {
161
+ * // Use any valid document ID — not the internal `versions.<releaseName>.<id>` format or "drafts.<id>" format.
162
+ * // New documents must be explicitly created with `createDocument` to become part of a release.
163
+ * const newDocHandle = createDocumentHandle({
164
+ * documentId: crypto.randomUUID(), // or the existing document ID you want to make a release version of
165
+ * documentType: 'article',
166
+ * perspective: {releaseName: 'summer-drop'},
167
+ * })
168
+ *
169
+ * apply(
170
+ * createDocument(newDocHandle, {
171
+ * title: 'New Article',
172
+ * author: 'John Doe',
173
+ * publishedAt: new Date().toISOString(),
174
+ * })
175
+ * )
176
+ * }
177
+ *
178
+ * return <button onClick={handleCreateArticle}>Create Article</button>
179
+ * }
180
+ * ```
181
+ *
182
+ * @example Edit an existing document in a release
183
+ * ```tsx
184
+ * import {
185
+ * editDocument,
186
+ * createDocumentHandle,
187
+ * useApplyDocumentActions
188
+ * } from '@sanity/sdk-react'
189
+ *
190
+ * function EditArticleInReleaseButton({documentId}: {documentId: string}) {
191
+ * const apply = useApplyDocumentActions()
192
+ *
193
+ * const handleEdit = () => {
194
+ * // Pass the document's regular ID — not `versions.<releaseName>.<id>`.
195
+ * // Documents that already have a version in the release can be edited directly with `editDocument`.
196
+ * const docHandle = createDocumentHandle({
197
+ * documentId,
198
+ * documentType: 'article',
199
+ * perspective: {releaseName: 'summer-drop'},
200
+ * })
201
+ *
202
+ * apply(editDocument(docHandle, {title: 'Updated for release'}))
203
+ * }
204
+ *
205
+ * return <button onClick={handleEdit}>Edit in Release</button>
206
+ * }
207
+ * ```
151
208
  */
152
209
  export const useApplyDocumentActions: UseApplyDocumentActions = () => {
153
- const instance = useSanityInstance()
154
- const resources = useContext(ResourcesContext)
155
- const contextResource = useContext(ResourceContext)
156
-
157
- return (actionOrActions, options) => {
158
- const actions = Array.isArray(actionOrActions) ? actionOrActions : [actionOrActions]
159
- const optionsResource = options
160
- ? normalizeResourceOptions(options, resources, contextResource).resource
161
- : undefined
162
-
163
- const normalizedActions = actions.map((action) =>
164
- normalizeResourceOptions(action, resources, contextResource),
165
- )
166
- let resource
167
-
168
- for (const action of normalizedActions) {
169
- if (!resource) resource = action.resource
170
- if (action.resource !== resource) {
171
- throw new Error(
172
- `Mismatched resources found in actions. All actions must belong to the same resource. Found "${JSON.stringify(action.resource)}" but expected "${JSON.stringify(resource)}".`,
173
- )
174
- }
175
- }
176
-
177
- if (optionsResource && resource && optionsResource !== resource) {
178
- throw new Error(
179
- `Mismatched resources found in actions. Found top-level resource "${JSON.stringify(optionsResource)}" but expected resource from action handles "${JSON.stringify(resource)}".`,
180
- )
181
- }
182
-
183
- const effectiveResource = resource ?? optionsResource
184
- if (!effectiveResource) {
185
- throw new Error('No resource found. Provide a resource via the action handle or context.')
186
- }
187
-
188
- return applyDocumentActions(instance, {
189
- actions,
190
- resource: effectiveResource,
191
- })
192
- }
210
+ return useApplyActions() as ReturnType<UseApplyDocumentActions>
193
211
  }
@@ -0,0 +1,83 @@
1
+ import {createDocument} from '@sanity/sdk'
2
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
3
+
4
+ import {renderHook} from '../../../test/test-utils'
5
+ import {useApplyDocumentActions} from './useApplyDocumentActions'
6
+ import {useCreateDocument} from './useCreateDocument'
7
+
8
+ vi.mock('./useApplyDocumentActions', () => ({
9
+ useApplyDocumentActions: vi.fn(),
10
+ }))
11
+
12
+ const typeHandle = {
13
+ documentType: 'book',
14
+ projectId: 'test',
15
+ dataset: 'test',
16
+ } as const
17
+
18
+ describe('useCreateDocument hook', () => {
19
+ beforeEach(() => {
20
+ vi.clearAllMocks()
21
+ })
22
+
23
+ afterEach(() => {
24
+ vi.restoreAllMocks()
25
+ })
26
+
27
+ it('applies a createDocument action with a generated id and initial values', async () => {
28
+ vi.spyOn(crypto, 'randomUUID').mockReturnValue('00000000-0000-0000-0000-000000000000')
29
+ const apply = vi.fn().mockResolvedValue({transactionId: 'tx1'})
30
+ vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
31
+
32
+ const {result} = renderHook(() => useCreateDocument(typeHandle))
33
+ const handle = await result.current({title: 'New Book'})
34
+
35
+ expect(apply).toHaveBeenCalledWith(
36
+ createDocument(
37
+ {...typeHandle, documentId: '00000000-0000-0000-0000-000000000000'},
38
+ {
39
+ title: 'New Book',
40
+ },
41
+ ),
42
+ )
43
+ expect(handle).toEqual({...typeHandle, documentId: '00000000-0000-0000-0000-000000000000'})
44
+ })
45
+
46
+ it('returns a handle carrying the generated id', async () => {
47
+ vi.spyOn(crypto, 'randomUUID').mockReturnValue('11111111-1111-1111-1111-111111111111')
48
+ const apply = vi.fn().mockResolvedValue({transactionId: 'tx2'})
49
+ vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
50
+
51
+ const {result} = renderHook(() => useCreateDocument(typeHandle))
52
+ const handle = await result.current()
53
+
54
+ expect(handle.documentId).toBe('11111111-1111-1111-1111-111111111111')
55
+ expect(handle.documentType).toBe('book')
56
+ })
57
+
58
+ it('uses the documentId supplied on the handle instead of generating one', async () => {
59
+ const apply = vi.fn().mockResolvedValue({transactionId: 'tx3'})
60
+ vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
61
+
62
+ const {result} = renderHook(() => useCreateDocument({...typeHandle, documentId: 'fixed-id'}))
63
+ const handle = await result.current()
64
+
65
+ expect(handle.documentId).toBe('fixed-id')
66
+ expect(apply).toHaveBeenCalledWith(
67
+ createDocument({...typeHandle, documentId: 'fixed-id'}, undefined),
68
+ )
69
+ })
70
+
71
+ it('uses a per-call documentId override over the handle id', async () => {
72
+ const apply = vi.fn().mockResolvedValue({transactionId: 'tx4'})
73
+ vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
74
+
75
+ const {result} = renderHook(() => useCreateDocument({...typeHandle, documentId: 'handle-id'}))
76
+ const handle = await result.current({title: 'Override'}, {documentId: 'override-id'})
77
+
78
+ expect(handle.documentId).toBe('override-id')
79
+ expect(apply).toHaveBeenCalledWith(
80
+ createDocument({...typeHandle, documentId: 'override-id'}, {title: 'Override'}),
81
+ )
82
+ })
83
+ })
@@ -0,0 +1,117 @@
1
+ import {createDocument} from '@sanity/sdk'
2
+ import {type SanityDocument} from 'groq'
3
+
4
+ import {type DocumentHandle, type DocumentTypeHandle} from '../../config/handles'
5
+ import {useSanityInstance} from '../context/useSanityInstance'
6
+ import {trackHookUsage} from '../helpers/useTrackHookUsage'
7
+ import {useApplyDocumentActions} from './useApplyDocumentActions'
8
+
9
+ type IgnoredKey = '_id' | '_type' | '_rev' | '_createdAt' | '_updatedAt'
10
+
11
+ /**
12
+ * Optional per-call overrides for {@link useCreateDocument}'s create function.
13
+ * @public
14
+ */
15
+ export interface CreateDocumentOverrides {
16
+ /**
17
+ * Use this document ID instead of generating one. Overrides any `documentId`
18
+ * supplied on the handle passed to `useCreateDocument`.
19
+ */
20
+ documentId?: string
21
+ }
22
+
23
+ // Overload 1: Typegen — infers the document shape from your schema.
24
+ /**
25
+ * @public
26
+ * Create a new document, relying on Typegen for the initial-value type.
27
+ *
28
+ * @param options - A document-type handle including `documentType`, an optional `documentId`, and optionally `projectId`/`dataset`/`perspective`.
29
+ * @returns A function that creates the document. It accepts optional initial field values and an optional `{documentId}` override,
30
+ * and resolves to the {@link DocumentHandle} of the created document (carrying the generated or supplied id).
31
+ */
32
+ export function useCreateDocument<
33
+ TDocumentType extends string = string,
34
+ TDataset extends string = string,
35
+ TProjectId extends string = string,
36
+ >(
37
+ options: DocumentTypeHandle<TDocumentType, TDataset, TProjectId>,
38
+ ): (
39
+ initialValue?: Partial<
40
+ Omit<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>, IgnoredKey>
41
+ >,
42
+ overrides?: CreateDocumentOverrides,
43
+ ) => Promise<DocumentHandle<TDocumentType, TDataset, TProjectId>>
44
+
45
+ // Overload 2: Explicit type `TData`.
46
+ /**
47
+ * @public
48
+ * Create a new document with an explicit type `TData`.
49
+ *
50
+ * @param options - A document-type handle including `documentType` and optionally `projectId`/`dataset`/`perspective`.
51
+ * @returns A function that creates the document. It accepts optional initial field values (typed against `TData`) and an
52
+ * optional `{documentId}` override, and resolves to the {@link DocumentHandle} of the created document.
53
+ */
54
+ export function useCreateDocument<TData extends Record<string, unknown>>(
55
+ options: DocumentTypeHandle,
56
+ ): (
57
+ initialValue?: Partial<Omit<TData, IgnoredKey>>,
58
+ overrides?: CreateDocumentOverrides,
59
+ ) => Promise<DocumentHandle>
60
+
61
+ /**
62
+ * @public
63
+ * Provides a function to create a new document and returns its handle.
64
+ *
65
+ * @category Documents
66
+ * @remarks
67
+ * This is the create counterpart to {@link useEditDocument}. It wraps
68
+ * {@link useApplyDocumentActions} and the `createDocument` action for the common
69
+ * single-document case, so you don't have to assemble the action by hand.
70
+ *
71
+ * It handles the document ID for you: if you don't supply one (on the handle or
72
+ * via the per-call `{documentId}` override), a UUID is generated. Either way the
73
+ * returned {@link DocumentHandle} carries that id, ready to pass to
74
+ * {@link useDocument}, {@link useEditDocument}, or your router.
75
+ *
76
+ * Unlike {@link useEditDocument}, this hook does not read existing document state,
77
+ * so it never suspends.
78
+ *
79
+ * For atomic create-and-publish, or for creating several documents in a single
80
+ * transaction, use {@link useApplyDocumentActions} with the `createDocument` and
81
+ * `publishDocument` action creators directly.
82
+ *
83
+ * @example Create a document and navigate to it
84
+ * ```tsx
85
+ * import {useCreateDocument} from '@sanity/sdk-react'
86
+ * import {useNavigate} from 'react-router-dom'
87
+ *
88
+ * function CreateArticleButton() {
89
+ * const createArticle = useCreateDocument({documentType: 'article'})
90
+ * const navigate = useNavigate()
91
+ *
92
+ * const handleClick = async () => {
93
+ * const handle = await createArticle({title: 'New Article'})
94
+ * navigate(`/articles/${handle.documentId}`)
95
+ * }
96
+ *
97
+ * return <button onClick={handleClick}>Create Article</button>
98
+ * }
99
+ * ```
100
+ */
101
+ export function useCreateDocument(
102
+ options: DocumentTypeHandle,
103
+ ): (
104
+ initialValue?: Record<string, unknown>,
105
+ overrides?: CreateDocumentOverrides,
106
+ ) => Promise<DocumentHandle> {
107
+ const instance = useSanityInstance()
108
+ trackHookUsage(instance, 'useCreateDocument')
109
+ const apply = useApplyDocumentActions()
110
+
111
+ return async (initialValue, overrides) => {
112
+ const documentId = overrides?.documentId ?? options.documentId ?? crypto.randomUUID()
113
+ const handle: DocumentHandle = {...options, documentId}
114
+ await apply(createDocument(handle, initialValue))
115
+ return handle
116
+ }
117
+ }
@@ -2,6 +2,7 @@
2
2
  import {getDocumentState, resolveDocument, type StateSource} from '@sanity/sdk'
3
3
  import {type SanityDocument} from '@sanity/types'
4
4
  import {renderHook} from '@testing-library/react'
5
+ import {type SchemaOrigin} from 'groq'
5
6
  import {beforeEach, describe, expect, it, vi} from 'vitest'
6
7
 
7
8
  import {ResourceProvider} from '../../context/ResourceProvider'
@@ -12,6 +13,40 @@ vi.mock('@sanity/sdk', async (importOriginal) => {
12
13
  return {...original, getDocumentState: vi.fn(), resolveDocument: vi.fn()}
13
14
  })
14
15
 
16
+ // Define a single generic TestDocument type
17
+ type UseDocumentTestType = SchemaOrigin<
18
+ SanityDocument & {
19
+ _type: 'use-document-test-type'
20
+ foo?: string
21
+ extra?: boolean
22
+ title?: string
23
+ nested?: {
24
+ value?: number
25
+ }
26
+ },
27
+ 'p.use-document-test-dataset'
28
+ >
29
+
30
+ type UseDocumentTestTypeAlt = SchemaOrigin<
31
+ SanityDocument & {
32
+ _type: 'use-document-test-type'
33
+ bar: string[]
34
+ nested?: {
35
+ value?: number
36
+ }
37
+ },
38
+ 'p.use-document-test-alt-dataset'
39
+ >
40
+
41
+ // Scope the TestDocument type to the project/datasets used in tests
42
+
43
+ declare module 'groq' {
44
+ interface SanitySchemas {
45
+ 'p.use-document-test-dataset': UseDocumentTestType
46
+ 'p.use-document-test-alt-dataset': UseDocumentTestTypeAlt
47
+ }
48
+ }
49
+
15
50
  const book: SanityDocument = {
16
51
  _id: 'doc1',
17
52
  foo: 'bar',
@@ -36,10 +71,7 @@ describe('useDocument hook', () => {
36
71
 
37
72
  const {result} = renderHook(() => useDocument({documentId: 'doc1', documentType: 'book'}), {
38
73
  wrapper: ({children}) => (
39
- <ResourceProvider
40
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
41
- fallback={null}
42
- >
74
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
43
75
  {children}
44
76
  </ResourceProvider>
45
77
  ),
@@ -74,10 +106,7 @@ describe('useDocument hook', () => {
74
106
  },
75
107
  {
76
108
  wrapper: ({children}) => (
77
- <ResourceProvider
78
- resource={{projectId: 'test-project', dataset: 'test-dataset'}}
79
- fallback={null}
80
- >
109
+ <ResourceProvider projectId="test-project" dataset="test-dataset" fallback={null}>
81
110
  {children}
82
111
  </ResourceProvider>
83
112
  ),