@sanity/sdk-react 0.0.0-rc.6 → 0.0.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.
Files changed (40) hide show
  1. package/README.md +5 -57
  2. package/dist/index.d.ts +1000 -438
  3. package/dist/index.js +324 -258
  4. package/dist/index.js.map +1 -1
  5. package/package.json +17 -16
  6. package/src/_exports/sdk-react.ts +4 -1
  7. package/src/components/SDKProvider.tsx +6 -1
  8. package/src/components/SanityApp.test.tsx +29 -47
  9. package/src/components/SanityApp.tsx +12 -11
  10. package/src/components/auth/AuthBoundary.test.tsx +177 -7
  11. package/src/components/auth/AuthBoundary.tsx +32 -2
  12. package/src/components/auth/ConfigurationError.ts +22 -0
  13. package/src/components/auth/LoginError.tsx +9 -3
  14. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +136 -0
  15. package/src/hooks/auth/useVerifyOrgProjects.tsx +48 -0
  16. package/src/hooks/client/useClient.ts +3 -3
  17. package/src/hooks/comlink/useManageFavorite.test.ts +276 -27
  18. package/src/hooks/comlink/useManageFavorite.ts +102 -51
  19. package/src/hooks/comlink/useWindowConnection.ts +3 -2
  20. package/src/hooks/document/useApplyDocumentActions.ts +105 -31
  21. package/src/hooks/document/useDocument.test.ts +41 -4
  22. package/src/hooks/document/useDocument.ts +198 -114
  23. package/src/hooks/document/useDocumentEvent.test.ts +5 -5
  24. package/src/hooks/document/useDocumentEvent.ts +67 -23
  25. package/src/hooks/document/useDocumentPermissions.ts +47 -8
  26. package/src/hooks/document/useDocumentSyncStatus.test.ts +12 -5
  27. package/src/hooks/document/useDocumentSyncStatus.ts +41 -14
  28. package/src/hooks/document/useEditDocument.test.ts +24 -6
  29. package/src/hooks/document/useEditDocument.ts +238 -133
  30. package/src/hooks/documents/useDocuments.test.tsx +1 -1
  31. package/src/hooks/documents/useDocuments.ts +153 -44
  32. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +1 -1
  33. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +120 -47
  34. package/src/hooks/projection/useProjection.ts +134 -46
  35. package/src/hooks/query/useQuery.test.tsx +4 -4
  36. package/src/hooks/query/useQuery.ts +115 -43
  37. package/src/hooks/releases/useActiveReleases.test.tsx +84 -0
  38. package/src/hooks/releases/useActiveReleases.ts +39 -0
  39. package/src/hooks/releases/usePerspective.test.tsx +120 -0
  40. package/src/hooks/releases/usePerspective.ts +50 -0
package/dist/index.d.ts CHANGED
@@ -9,25 +9,31 @@ import {DatasetsResponse} from '@sanity/client'
9
9
  import {DocumentAction} from '@sanity/sdk'
10
10
  import {DocumentEvent} from '@sanity/sdk'
11
11
  import {DocumentHandle} from '@sanity/sdk'
12
+ import {DocumentOptions} from '@sanity/sdk'
12
13
  import {DocumentPermissionsResult} from '@sanity/sdk'
13
14
  import {FallbackProps} from 'react-error-boundary'
15
+ import {FavoriteStatusResponse} from '@sanity/sdk'
14
16
  import {FrameMessage} from '@sanity/sdk'
15
17
  import {GetUsersOptions} from '@sanity/sdk'
16
18
  import {JsonMatch} from '@sanity/sdk'
17
- import {JsonMatchPath} from '@sanity/sdk'
18
19
  import {MediaResource} from '@sanity/message-protocol'
20
+ import {PerspectiveHandle} from '@sanity/sdk'
19
21
  import {PreviewValue} from '@sanity/sdk'
20
22
  import {ProjectHandle} from '@sanity/sdk'
21
23
  import {QueryOptions} from '@sanity/sdk'
22
24
  import {ReactElement} from 'react'
23
25
  import {ReactNode} from 'react'
26
+ import {ReleaseDocument} from '@sanity/sdk'
24
27
  import {SanityClient} from '@sanity/client'
25
28
  import {SanityConfig} from '@sanity/sdk'
26
29
  import {SanityDocument} from '@sanity/types'
30
+ import {SanityDocumentResult} from 'groq'
27
31
  import {SanityInstance} from '@sanity/sdk'
28
32
  import {SanityProject} from '@sanity/sdk'
29
33
  import {SanityProject as SanityProject_2} from '@sanity/client'
34
+ import {SanityProjectionResult} from 'groq'
30
35
  import {SanityProjectMember} from '@sanity/client'
36
+ import {SanityQueryResult} from 'groq'
31
37
  import {SanityUser} from '@sanity/sdk'
32
38
  import {SortOrderingItem} from '@sanity/types'
33
39
  import {StudioResource} from '@sanity/message-protocol'
@@ -61,9 +67,9 @@ export declare function AuthBoundary({
61
67
  }: AuthBoundaryProps): React.ReactNode
62
68
 
63
69
  /**
64
- * @public
70
+ * @internal
65
71
  */
66
- declare interface AuthBoundaryProps {
72
+ export declare interface AuthBoundaryProps {
67
73
  /**
68
74
  * Custom component to render the login screen.
69
75
  * Receives all props. Defaults to {@link Login}.
@@ -88,10 +94,22 @@ declare interface AuthBoundaryProps {
88
94
  LoginErrorComponent?: React.ComponentType<LoginErrorProps>
89
95
  /** Header content to display */
90
96
  header?: React.ReactNode
97
+ /**
98
+ * The project IDs to use for organization verification.
99
+ */
100
+ projectIds?: string[]
91
101
  /** Footer content to display */
92
102
  footer?: React.ReactNode
93
103
  /** Protected content to render when authenticated */
94
104
  children?: React.ReactNode
105
+ /**
106
+ * Whether to verify that the project belongs to the organization specified in the dashboard context.
107
+ * By default, organization verification is enabled when running in a dashboard context.
108
+ *
109
+ * WARNING: Disabling organization verification is NOT RECOMMENDED and may cause your application
110
+ * to break in the future. This should never be disabled in production environments.
111
+ */
112
+ verifyOrganization?: boolean
95
113
  }
96
114
 
97
115
  /**
@@ -124,7 +142,16 @@ declare interface DocumentInteractionHistory {
124
142
  * @beta
125
143
  * @category Types
126
144
  */
127
- export declare interface DocumentsOptions extends QueryOptions {
145
+ export declare interface DocumentsOptions<
146
+ TDocumentType extends string = string,
147
+ TDataset extends string = string,
148
+ TProjectId extends string = string,
149
+ > extends DatasetHandle<TDataset, TProjectId>,
150
+ Pick<QueryOptions, 'perspective' | 'params'> {
151
+ /**
152
+ * Filter documents by their `_type`. Can be a single type or an array of types.
153
+ */
154
+ documentType?: TDocumentType | TDocumentType[]
128
155
  /**
129
156
  * GROQ filter expression to apply to the query
130
157
  */
@@ -149,11 +176,15 @@ export declare interface DocumentsOptions extends QueryOptions {
149
176
  * @beta
150
177
  * @category Types
151
178
  */
152
- export declare interface DocumentsResponse {
179
+ export declare interface DocumentsResponse<
180
+ TDocumentType extends string = string,
181
+ TDataset extends string = string,
182
+ TProjectId extends string = string,
183
+ > {
153
184
  /**
154
185
  * Array of document handles for the current batch
155
186
  */
156
- data: DocumentHandle[]
187
+ data: DocumentHandle<TDocumentType, TDataset, TProjectId>[]
157
188
  /**
158
189
  * Whether there are more items available to load
159
190
  */
@@ -202,9 +233,9 @@ export declare interface FrameConnection<TFrameMessage extends FrameMessage> {
202
233
  */
203
234
  declare type LoginErrorProps = FallbackProps
204
235
 
205
- declare interface ManageFavorite {
206
- favorite: () => void
207
- unfavorite: () => void
236
+ declare interface ManageFavorite extends FavoriteStatusResponse {
237
+ favorite: () => Promise<void>
238
+ unfavorite: () => Promise<void>
208
239
  isFavorited: boolean
209
240
  isConnected: boolean
210
241
  }
@@ -236,7 +267,12 @@ export declare interface NavigateToStudioResult {
236
267
  * @beta
237
268
  * @category Types
238
269
  */
239
- export declare interface PaginatedDocumentsOptions extends QueryOptions {
270
+ export declare interface PaginatedDocumentsOptions<
271
+ TDocumentType extends string = string,
272
+ TDataset extends string = string,
273
+ TProjectId extends string = string,
274
+ > extends Omit<QueryOptions<TDocumentType, TDataset, TProjectId>, 'query'> {
275
+ documentType?: TDocumentType | TDocumentType[]
240
276
  /**
241
277
  * GROQ filter expression to apply to the query
242
278
  */
@@ -261,11 +297,15 @@ export declare interface PaginatedDocumentsOptions extends QueryOptions {
261
297
  * @beta
262
298
  * @category Types
263
299
  */
264
- export declare interface PaginatedDocumentsResponse {
300
+ export declare interface PaginatedDocumentsResponse<
301
+ TDocumentType extends string = string,
302
+ TDataset extends string = string,
303
+ TProjectId extends string = string,
304
+ > {
265
305
  /**
266
306
  * Array of document handles for the current page
267
307
  */
268
- data: DocumentHandle[]
308
+ data: DocumentHandle<TDocumentType, TDataset, TProjectId>[]
269
309
  /**
270
310
  * Whether a query is currently in progress
271
311
  */
@@ -417,8 +457,12 @@ export declare interface ResourceProviderProps extends SanityConfig {
417
457
  * as well as application context and state which is used by the Sanity React hooks. Your application
418
458
  * must be wrapped with the SanityApp component to function properly.
419
459
  *
420
- * SanityApp creates a hierarchy of ResourceProviders, each providing a SanityInstance that can be
421
- * accessed by hooks. The first configuration in the array becomes the default instance.
460
+ * The `config` prop on the SanityApp component accepts either a single {@link SanityConfig} object, or an array of them.
461
+ * This allows your app to work with one or more of your organization’s datasets.
462
+ *
463
+ * @remarks
464
+ * When passing multiple SanityConfig objects to the `config` prop, the first configuration in the array becomes the default
465
+ * configuration used by the App SDK Hooks.
422
466
  *
423
467
  * @category Components
424
468
  * @param props - Your Sanity configuration and the React children to render
@@ -426,18 +470,18 @@ export declare interface ResourceProviderProps extends SanityConfig {
426
470
  *
427
471
  * @example
428
472
  * ```tsx
429
- * import { SanityApp } from '@sanity/sdk-react'
473
+ * import { SanityApp, type SanityConfig } from '@sanity/sdk-react'
430
474
  *
431
475
  * import MyAppRoot from './Root'
432
476
  *
433
477
  * // Single project configuration
434
- * const mySanityConfig = {
478
+ * const mySanityConfig: SanityConfig = {
435
479
  * projectId: 'my-project-id',
436
480
  * dataset: 'production',
437
481
  * }
438
482
  *
439
483
  * // Or multiple project configurations
440
- * const multipleConfigs = [
484
+ * const multipleConfigs: SanityConfig[] = [
441
485
  * // Configuration for your main project. This will be used as the default project for hooks.
442
486
  * {
443
487
  * projectId: 'marketing-website-project',
@@ -457,7 +501,7 @@ export declare interface ResourceProviderProps extends SanityConfig {
457
501
  *
458
502
  * export default function MyApp() {
459
503
  * return (
460
- * <SanityApp config={mySanityConfig} fallback={<LoadingSpinner />}>
504
+ * <SanityApp config={mySanityConfig} fallback={<div>Loading…</div>}>
461
505
  * <MyAppRoot />
462
506
  * </SanityApp>
463
507
  * )
@@ -468,7 +512,6 @@ export declare function SanityApp({
468
512
  children,
469
513
  fallback,
470
514
  config,
471
- sanityConfigs,
472
515
  ...props
473
516
  }: SanityAppProps): ReactElement
474
517
 
@@ -519,57 +562,141 @@ declare interface StudioWorkspacesResult {
519
562
  isConnected: boolean
520
563
  }
521
564
 
522
- declare type Updater<TValue> = TValue | ((nextValue: TValue) => TValue)
565
+ declare type Updater<TValue> = TValue | ((currentValue: TValue) => TValue)
566
+
567
+ /**
568
+ * @public
569
+ */
570
+ declare type UseActiveReleases = {
571
+ (): ReleaseDocument[]
572
+ }
523
573
 
524
574
  /**
575
+ * @public
576
+
577
+ * Returns the active releases for the current project,
578
+ * represented as a list of release documents.
525
579
  *
580
+ * @returns The active releases for the current project.
581
+ * @category Projects
582
+ * @example
583
+ * ```tsx
584
+ * import {useActiveReleases} from '@sanity/sdk-react'
585
+ *
586
+ * const activeReleases = useActiveReleases()
587
+ * ```
588
+ */
589
+ export declare const useActiveReleases: UseActiveReleases
590
+
591
+ /**
526
592
  * @beta
593
+ */
594
+ declare interface UseApplyDocumentActions {
595
+ (): <
596
+ TDocumentType extends string = string,
597
+ TDataset extends string = string,
598
+ TProjectId extends string = string,
599
+ >(
600
+ action:
601
+ | DocumentAction<TDocumentType, TDataset, TProjectId>
602
+ | DocumentAction<TDocumentType, TDataset, TProjectId>[],
603
+ options?: ApplyDocumentActionsOptions,
604
+ ) => Promise<ActionsResult<SanityDocumentResult<TDocumentType, TDataset, TProjectId>>>
605
+ }
606
+
607
+ /**
608
+ * @beta
609
+ *
610
+ * Provides a stable callback function for applying one or more document actions.
527
611
  *
528
- * Provides a callback for applying one or more actions to a document.
612
+ * This hook wraps the core `applyDocumentActions` functionality from `@sanity/sdk`,
613
+ * integrating it with the React component lifecycle and {@link SanityInstance}.
614
+ * It allows you to apply actions generated by functions like `createDocument`,
615
+ * `editDocument`, `deleteDocument`, `publishDocument`, `unpublishDocument`,
616
+ * and `discardDocument` to documents.
617
+ *
618
+ * Features:
619
+ * - Applies one or multiple `DocumentAction` objects.
620
+ * - Supports optimistic updates: Local state reflects changes immediately.
621
+ * - Handles batching: Multiple actions passed together are sent as a single atomic transaction.
622
+ * - Integrates with the collaborative editing engine for conflict resolution and state synchronization.
529
623
  *
530
624
  * @category Documents
531
- * @param dataset - An optional dataset handle with projectId and dataset. If not provided, the nearest SanityInstance from context will be used.
532
- * @returns A function that takes one more more {@link DocumentAction}s and returns a promise that resolves to an {@link ActionsResult}.
625
+ * @returns A stable callback function. When called with a single `DocumentAction` or an array of `DocumentAction`s,
626
+ * it returns a promise that resolves to an {@link ActionsResult}. The `ActionsResult` contains information about the
627
+ * outcome, including optimistic results if applicable.
628
+ *
629
+ * @remarks
630
+ * This hook is a fundamental part of interacting with document state programmatically.
631
+ * It operates within the same unified pipeline as other document hooks like `useDocument` (for reading state)
632
+ * and {@link useEditDocument} (a higher-level hook specifically for edits).
633
+ *
634
+ * When multiple actions are provided in a single call, they are guaranteed to be submitted
635
+ * as a single transaction to Content Lake. This ensures atomicity for related operations (e.g., creating and publishing a document).
636
+ *
637
+ * @function
638
+ *
533
639
  * @example Publish or unpublish a document
534
- * ```
535
- * import { publishDocument, unpublishDocument } from '@sanity/sdk'
536
- * import { useApplyDocumentActions } from '@sanity/sdk-react'
640
+ * ```tsx
641
+ * import {
642
+ * publishDocument,
643
+ * unpublishDocument,
644
+ * useApplyDocumentActions,
645
+ * type DocumentHandle
646
+ * } from '@sanity/sdk-react'
647
+ *
648
+ * // Define props using the DocumentHandle type
649
+ * interface PublishControlsProps {
650
+ * doc: DocumentHandle
651
+ * }
537
652
  *
538
- * const apply = useApplyDocumentActions()
539
- * const myDocument = { documentId: 'my-document-id', documentType: 'my-document-type' }
653
+ * function PublishControls({doc}: PublishControlsProps) {
654
+ * const apply = useApplyDocumentActions()
540
655
  *
541
- * return (
542
- * <button onClick={() => apply(publishDocument(myDocument))}>Publish</button>
543
- * <button onClick={() => apply(unpublishDocument(myDocument))}>Unpublish</button>
544
- * )
545
- * ```
656
+ * const handlePublish = () => apply(publishDocument(doc))
657
+ * const handleUnpublish = () => apply(unpublishDocument(doc))
546
658
  *
547
- * @example Create and publish a new document
659
+ * return (
660
+ * <>
661
+ * <button onClick={handlePublish}>Publish</button>
662
+ * <button onClick={handleUnpublish}>Unpublish</button>
663
+ * </>
664
+ * )
665
+ * }
548
666
  * ```
549
- * import { createDocument, publishDocument } from '@sanity/sdk'
550
- * import { useApplyDocumentActions } from '@sanity/sdk-react'
551
667
  *
552
- * const apply = useApplyDocumentActions()
668
+ * @example Create and publish a new document
669
+ * ```tsx
670
+ * import {
671
+ * createDocument,
672
+ * publishDocument,
673
+ * createDocumentHandle,
674
+ * useApplyDocumentActions
675
+ * } from '@sanity/sdk-react'
676
+ *
677
+ * function CreateAndPublishButton({documentType}: {documentType: string}) {
678
+ * const apply = useApplyDocumentActions()
679
+ *
680
+ * const handleCreateAndPublish = () => {
681
+ * // Create a new handle inside the handler
682
+ * const newDocHandle = createDocumentHandle({ documentId: crypto.randomUUID(), documentType })
683
+ *
684
+ * // Apply multiple actions for the new handle as a single transaction
685
+ * apply([
686
+ * createDocument(newDocHandle),
687
+ * publishDocument(newDocHandle),
688
+ * ])
689
+ * }
553
690
  *
554
- * const handleCreateAndPublish = () => {
555
- * const handle = { documentId: window.crypto.randomUUID(), documentType: 'my-document-type' }
556
- * apply([
557
- * createDocument(handle),
558
- * publishDocument(handle),
559
- * ])
691
+ * return (
692
+ * <button onClick={handleCreateAndPublish}>
693
+ * I'm feeling lucky
694
+ * </button>
695
+ * )
560
696
  * }
561
- *
562
- * return (
563
- * <button onClick={handleCreateAndPublish}>
564
- * I'm feeling lucky
565
- * </button>
566
- * )
567
697
  * ```
568
698
  */
569
- export declare const useApplyDocumentActions: () => (
570
- action: DocumentAction | DocumentAction[],
571
- options?: ApplyDocumentActionsOptions | undefined,
572
- ) => Promise<ActionsResult<SanityDocument>>
699
+ export declare const useApplyDocumentActions: UseApplyDocumentActions
573
700
 
574
701
  /**
575
702
  * @internal
@@ -603,11 +730,11 @@ export declare const useAuthToken: () => string | null
603
730
 
604
731
  /**
605
732
  * A React hook that provides a client that subscribes to changes in your application,
606
- * such as user authentication changes.
607
733
  *
608
734
  * @remarks
609
- * The hook uses `useSyncExternalStore` to safely subscribe to changes
610
- * and ensure consistency between server and client rendering.
735
+ * This hook is intended for advanced use cases and special API calls that the React SDK
736
+ * does not yet provide hooks for. We welcome you to get in touch with us to let us know
737
+ * your use cases for this!
611
738
  *
612
739
  * @category Platform
613
740
  * @returns A Sanity client
@@ -713,122 +840,259 @@ declare type UseDatasets = {
713
840
  */
714
841
  export declare const useDatasets: UseDatasets
715
842
 
843
+ declare interface UseDocument {
844
+ /** @internal */
845
+ <TDocumentType extends string, TDataset extends string, TProjectId extends string = string>(
846
+ options: DocumentOptions<undefined, TDocumentType, TDataset, TProjectId>,
847
+ ): SanityDocumentResult<TDocumentType, TDataset, TProjectId> | null
848
+ /** @internal */
849
+ <
850
+ TPath extends string,
851
+ TDocumentType extends string,
852
+ TDataset extends string = string,
853
+ TProjectId extends string = string,
854
+ >(
855
+ options: DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>,
856
+ ): JsonMatch<SanityDocumentResult<TDocumentType, TDataset, TProjectId>, TPath> | undefined
857
+ /** @internal */
858
+ <TData>(options: DocumentOptions<undefined>): TData | null
859
+ /** @internal */
860
+ <TData>(options: DocumentOptions<string>): TData | undefined
861
+ /**
862
+ * ## useDocument via Type Inference (Recommended)
863
+ *
864
+ * @beta
865
+ *
866
+ * The preferred way to use this hook when working with Sanity Typegen.
867
+ *
868
+ * Features:
869
+ * - Automatically infers document types from your schema
870
+ * - Provides type-safe access to documents and nested fields
871
+ * - Supports project/dataset-specific type inference
872
+ * - Works seamlessly with Typegen-generated types
873
+ *
874
+ * This hook will suspend while the document data is being fetched and loaded.
875
+ *
876
+ * When fetching a full document:
877
+ * - Returns the complete document object if it exists
878
+ * - Returns `null` if the document doesn't exist
879
+ *
880
+ * When fetching with a path:
881
+ * - Returns the value at the specified path if both the document and path exist
882
+ * - Returns `undefined` if either the document doesn't exist or the path doesn't exist in the document
883
+ *
884
+ * @category Documents
885
+ * @param options - Configuration including `documentId`, `documentType`, and optionally:
886
+ * - `path`: To select a nested value (returns typed value at path)
887
+ * - `projectId`/`dataset`: For multi-project/dataset setups
888
+ * @returns The document state (or nested value if path provided).
889
+ *
890
+ * @example Basic document fetch
891
+ * ```tsx
892
+ * import {useDocument, type DocumentHandle} from '@sanity/sdk-react'
893
+ *
894
+ * interface ProductViewProps {
895
+ * doc: DocumentHandle<'product'> // Typegen infers product type
896
+ * }
897
+ *
898
+ * function ProductView({doc}: ProductViewProps) {
899
+ * const product = useDocument({...doc}) // Fully typed product
900
+ * return <h1>{product.title ?? 'Untitled'}</h1>
901
+ * }
902
+ * ```
903
+ *
904
+ * @example Fetching a specific field
905
+ * ```tsx
906
+ * import {useDocument, type DocumentHandle} from '@sanity/sdk-react'
907
+ *
908
+ * interface ProductTitleProps {
909
+ * doc: DocumentHandle<'product'>
910
+ * }
911
+ *
912
+ * function ProductTitle({doc}: ProductTitleProps) {
913
+ * const title = useDocument({
914
+ * ...doc,
915
+ * path: 'title' // Returns just the title field
916
+ * })
917
+ * return <h1>{title ?? 'Untitled'}</h1>
918
+ * }
919
+ * ```
920
+ *
921
+ * @inlineType DocumentOptions
922
+ */
923
+ <
924
+ TPath extends string | undefined = undefined,
925
+ TDocumentType extends string = string,
926
+ TDataset extends string = string,
927
+ TProjectId extends string = string,
928
+ >(
929
+ options: DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>,
930
+ ): TPath extends string
931
+ ? JsonMatch<SanityDocumentResult<TDocumentType, TDataset, TProjectId>, TPath> | undefined
932
+ : SanityDocumentResult<TDocumentType, TDataset, TProjectId> | null
933
+ /**
934
+ * @beta
935
+ *
936
+ * ## useDocument via Explicit Types
937
+ *
938
+ * Use this version when:
939
+ * - You're not using Sanity Typegen
940
+ * - You need to manually specify document types
941
+ * - You're working with dynamic document types
942
+ *
943
+ * Key differences from Typegen version:
944
+ * - Requires manual type specification via `TData`
945
+ * - Returns `TData | null` for full documents
946
+ * - Returns `TData | undefined` for nested values
947
+ *
948
+ * This hook will suspend while the document data is being fetched.
949
+ *
950
+ * @typeParam TData - The explicit type for the document or field
951
+ * @typeParam TPath - Optional path to a nested value
952
+ * @param options - Configuration including `documentId` and optionally:
953
+ * - `path`: To select a nested value
954
+ * - `projectId`/`dataset`: For multi-project/dataset setups
955
+ * @returns The document state (or nested value if path provided)
956
+ *
957
+ * @example Basic document fetch with explicit type
958
+ * ```tsx
959
+ * import {useDocument, type DocumentHandle, type SanityDocument} from '@sanity/sdk-react'
960
+ *
961
+ * interface Book extends SanityDocument {
962
+ * _type: 'book'
963
+ * title: string
964
+ * author: string
965
+ * }
966
+ *
967
+ * interface BookViewProps {
968
+ * doc: DocumentHandle
969
+ * }
970
+ *
971
+ * function BookView({doc}: BookViewProps) {
972
+ * const book = useDocument<Book>({...doc})
973
+ * return <h1>{book?.title ?? 'Untitled'} by {book?.author ?? 'Unknown'}</h1>
974
+ * }
975
+ * ```
976
+ *
977
+ * @example Fetching a specific field with explicit type
978
+ * ```tsx
979
+ * import {useDocument, type DocumentHandle} from '@sanity/sdk-react'
980
+ *
981
+ * interface BookTitleProps {
982
+ * doc: DocumentHandle
983
+ * }
984
+ *
985
+ * function BookTitle({doc}: BookTitleProps) {
986
+ * const title = useDocument<string>({...doc, path: 'title'})
987
+ * return <h1>{title ?? 'Untitled'}</h1>
988
+ * }
989
+ * ```
990
+ *
991
+ * @inlineType DocumentOptions
992
+ */
993
+ <TData, TPath extends string>(
994
+ options: DocumentOptions<TPath>,
995
+ ): TPath extends string ? TData | undefined : TData | null
996
+ /**
997
+ * @internal
998
+ */
999
+ (options: DocumentOptions): unknown
1000
+ }
1001
+
716
1002
  /**
717
1003
  * @beta
1004
+ * Reads and subscribes to a document's realtime state, incorporating both local and remote changes.
718
1005
  *
719
- * ## useDocument(doc, path)
720
- * Read and subscribe to nested values in a document
721
- * @category Documents
722
- * @param doc - The document to read state from, specified as a DocumentHandle
723
- * @param path - The path to the nested value to read from
724
- * @returns The value at the specified path
725
- * @example
726
- * ```tsx
727
- * import {useDocument} from '@sanity/sdk-react'
1006
+ * This hook comes in two main flavors to suit your needs:
728
1007
  *
729
- * const documentHandle = {
730
- * documentId: 'order-123',
731
- * documentType: 'order',
732
- * projectId: 'abc123',
733
- * dataset: 'production'
734
- * }
1008
+ * 1. **[Type Inference](#usedocument-via-type-inference-recommended)** (Recommended) - Automatically gets types from your Sanity schema
1009
+ * 2. **[Explicit Types](#usedocument-via-explicit-types)** - Manually specify types when needed
735
1010
  *
736
- * function OrderLink() {
737
- * const title = useDocument(documentHandle, 'title')
738
- * const id = useDocument(documentHandle, '_id')
1011
+ * @remarks
1012
+ * `useDocument` is ideal for realtime editing interfaces where you need immediate feedback on changes.
1013
+ * However, it can be resource-intensive since it maintains a realtime connection.
739
1014
  *
740
- * return (
741
- * <a href={`/order/${id}`}>Order {title} today!</a>
742
- * )
743
- * }
744
- * ```
1015
+ * For simpler cases where:
1016
+ * - You only need to display content
1017
+ * - Realtime updates aren't critical
1018
+ * - You want better performance
1019
+ *
1020
+ * …consider using {@link useProjection} or {@link useQuery} instead. These hooks are more efficient
1021
+ * for read-heavy applications.
745
1022
  *
1023
+ * @function
746
1024
  */
747
- export declare function useDocument<
748
- TDocument extends SanityDocument,
749
- TPath extends JsonMatchPath<TDocument>,
750
- >(doc: DocumentHandle<TDocument>, path: TPath): JsonMatch<TDocument, TPath> | undefined
1025
+ export declare const useDocument: UseDocument
751
1026
 
752
1027
  /**
1028
+ *
753
1029
  * @beta
754
- * ## useDocument(doc)
755
- * Read and subscribe to an entire document
756
- * @param doc - The document to read state from, specified as a DocumentHandle
757
- * @returns The document state as an object
758
- * @example
759
- * ```tsx
760
- * import {type SanityDocument, useDocument} from '@sanity/sdk-react'
761
1030
  *
762
- * interface Book extends SanityDocument {
763
- * title: string
764
- * author: string
765
- * summary: string
766
- * }
1031
+ * Subscribes an event handler to events in your application's document store.
767
1032
  *
768
- * const documentHandle = {
769
- * documentId: 'book-123',
770
- * documentType: 'book',
771
- * projectId: 'abc123',
772
- * dataset: 'production'
773
- * }
1033
+ * @category Documents
1034
+ * @param options - An object containing the event handler (`onEvent`) and optionally a `DatasetHandle` (projectId and dataset). If the handle is not provided, the nearest Sanity instance from context will be used.
1035
+ * @example Creating a custom hook for document event toasts
1036
+ * ```tsx
1037
+ * import {createDatasetHandle, type DatasetHandle, type DocumentEvent, useDocumentEvent} from '@sanity/sdk-react'
1038
+ * import {useToast} from './my-ui-library'
774
1039
  *
775
- * function DocumentView() {
776
- * const book = useDocument<Book>(documentHandle)
1040
+ * // Define options for the custom hook, extending DatasetHandle
1041
+ * interface DocumentToastsOptions extends DatasetHandle {
1042
+ * // Could add more options, e.g., { includeEvents: DocumentEvent['type'][] }
1043
+ * }
777
1044
  *
778
- * if (!book) {
779
- * return <div>Loading...</div>
1045
+ * // Define the custom hook
1046
+ * function useDocumentToasts({...datasetHandle}: DocumentToastsOptions = {}) {
1047
+ * const showToast = useToast() // Get the toast function
1048
+ *
1049
+ * // Define the event handler logic to show toasts on specific events
1050
+ * const handleEvent = (event: DocumentEvent) => {
1051
+ * if (event.type === 'published') {
1052
+ * showToast(`Document ${event.documentId} published.`)
1053
+ * } else if (event.type === 'unpublished') {
1054
+ * showToast(`Document ${event.documentId} unpublished.`)
1055
+ * } else if (event.type === 'deleted') {
1056
+ * showToast(`Document ${event.documentId} deleted.`)
1057
+ * } else {
1058
+ * // Optionally log other events for debugging
1059
+ * console.log('Document Event:', event.type, event.documentId)
1060
+ * }
780
1061
  * }
781
1062
  *
782
- * return (
783
- * <article>
784
- * <h1>{book.title}</h1>
785
- * <address>By {book.author}</address>
1063
+ * // Call the original hook, spreading the handle properties
1064
+ * useDocumentEvent({
1065
+ * ...datasetHandle, // Spread the dataset handle (projectId, dataset)
1066
+ * onEvent: handleEvent,
1067
+ * })
1068
+ * }
786
1069
  *
787
- * <h2>Summary</h2>
788
- * {book.summary}
1070
+ * function MyComponentWithToasts() {
1071
+ * // Use the custom hook, passing specific handle info
1072
+ * const specificHandle = createDatasetHandle({ projectId: 'p1', dataset: 'ds1' })
1073
+ * useDocumentToasts(specificHandle)
789
1074
  *
790
- * <h2>Order</h2>
791
- * <a href={`/order/${book._id}`}>Order {book.title} today!</a>
792
- * </article>
793
- * )
1075
+ * // // Or use it relying on context for the handle
1076
+ * // useDocumentToasts()
1077
+ *
1078
+ * return <div>...</div>
794
1079
  * }
795
1080
  * ```
796
- *
797
1081
  */
798
- export declare function useDocument<TDocument extends SanityDocument>(
799
- doc: DocumentHandle<TDocument>,
800
- ): TDocument | null
1082
+ export declare function useDocumentEvent<
1083
+ TDataset extends string = string,
1084
+ TProjectId extends string = string,
1085
+ >(options: UseDocumentEventOptions<TDataset, TProjectId>): void
801
1086
 
802
1087
  /**
803
- *
804
1088
  * @beta
805
- *
806
- * Subscribes an event handler to events in your application's document store, such as document
807
- * creation, deletion, and updates.
808
- *
809
- * @category Documents
810
- * @param handler - The event handler to register.
811
- * @param doc - The document to subscribe to events for. If you pass a `DocumentHandle` with specified `projectId` and `dataset`,
812
- * the document will be read from the specified Sanity project and dataset that is included in the handle. If no `projectId` or `dataset` is provided,
813
- * the document will use the nearest instance from context.
814
- * @example
815
- * ```
816
- * import {useDocumentEvent} from '@sanity/sdk-react'
817
- * import {type DocumentEvent} from '@sanity/sdk'
818
- *
819
- * useDocumentEvent((event) => {
820
- * if (event.type === DocumentEvent.DocumentDeletedEvent) {
821
- * alert(`Document with ID ${event.documentId} deleted!`)
822
- * } else {
823
- * console.log(event)
824
- * }
825
- * })
826
- * ```
827
1089
  */
828
- export declare function useDocumentEvent(
829
- handler: (documentEvent: DocumentEvent) => void,
830
- dataset: DatasetHandle,
831
- ): void
1090
+ declare interface UseDocumentEventOptions<
1091
+ TDataset extends string = string,
1092
+ TProjectId extends string = string,
1093
+ > extends DatasetHandle<TDataset, TProjectId> {
1094
+ onEvent: (documentEvent: DocumentEvent) => void
1095
+ }
832
1096
 
833
1097
  /**
834
1098
  *
@@ -837,23 +1101,41 @@ export declare function useDocumentEvent(
837
1101
  * Check if the current user has the specified permissions for the given document actions.
838
1102
  *
839
1103
  * @category Permissions
840
- * @param actionOrActions - One more more calls to a particular document action function for a given document
1104
+ * @param actionOrActions - One or more document action functions (e.g., `publishDocument(handle)`).
841
1105
  * @returns An object that specifies whether the action is allowed; if the action is not allowed, an explanatory message and list of reasons is also provided.
842
1106
  *
1107
+ * @remarks
1108
+ * When passing multiple actions, all actions must belong to the same project and dataset.
1109
+ * Note, however, that you can check permissions on multiple documents from the same project and dataset (as in the second example below).
1110
+ *
843
1111
  * @example Checking for permission to publish a document
844
- * ```ts
845
- * import {useDocumentPermissions, useApplyDocumentActions} from '@sanity/sdk-react'
846
- * import {publishDocument} from '@sanity/sdk'
1112
+ * ```tsx
1113
+ * import {
1114
+ * useDocumentPermissions,
1115
+ * useApplyDocumentActions,
1116
+ * publishDocument,
1117
+ * createDocumentHandle,
1118
+ * type DocumentHandle
1119
+ * } from '@sanity/sdk-react'
1120
+ *
1121
+ * // Define props using the DocumentHandle type
1122
+ * interface PublishButtonProps {
1123
+ * doc: DocumentHandle
1124
+ * }
1125
+ *
1126
+ * function PublishButton({doc}: PublishButtonProps) {
1127
+ * const publishAction = publishDocument(doc)
847
1128
  *
848
- * export function PublishButton({doc}: {doc: DocumentHandle}) {
849
- * const publishPermissions = useDocumentPermissions(publishDocument(doc))
850
- * const applyAction = useApplyDocumentActions()
1129
+ * // Pass the same action call to check permissions
1130
+ * const publishPermissions = useDocumentPermissions(publishAction)
1131
+ * const apply = useApplyDocumentActions()
851
1132
  *
852
1133
  * return (
853
1134
  * <>
854
1135
  * <button
855
1136
  * disabled={!publishPermissions.allowed}
856
- * onClick={() => applyAction(publishDocument(doc))}
1137
+ * // Pass the same action call to apply the action
1138
+ * onClick={() => apply(publishAction)}
857
1139
  * popoverTarget={`${publishPermissions.allowed ? undefined : 'publishButtonPopover'}`}
858
1140
  * >
859
1141
  * Publish
@@ -866,6 +1148,27 @@ export declare function useDocumentEvent(
866
1148
  * </>
867
1149
  * )
868
1150
  * }
1151
+ *
1152
+ * // Usage:
1153
+ * // const doc = createDocumentHandle({ documentId: 'doc1', documentType: 'myType' })
1154
+ * // <PublishButton doc={doc} />
1155
+ * ```
1156
+ *
1157
+ * @example Checking for permissions to edit multiple documents
1158
+ * ```tsx
1159
+ * import {
1160
+ * useDocumentPermissions,
1161
+ * editDocument,
1162
+ * type DocumentHandle
1163
+ * } from '@sanity/sdk-react'
1164
+ *
1165
+ * export default function canEditMultiple(docHandles: DocumentHandle[]) {
1166
+ * // Create an array containing an editDocument action for each of the document handles
1167
+ * const editActions = docHandles.map(doc => editDocument(doc))
1168
+ *
1169
+ * // Return the result of checking for edit permissions on all of the document handles
1170
+ * return useDocumentPermissions(editActions)
1171
+ * }
869
1172
  * ```
870
1173
  */
871
1174
  export declare function useDocumentPermissions(
@@ -889,38 +1192,115 @@ export declare function useDocumentPermissions(
889
1192
  *
890
1193
  * @example Basic infinite list with loading more
891
1194
  * ```tsx
892
- * const { data, hasMore, isPending, loadMore, count } = useDocuments({
893
- * filter: '_type == "post"',
894
- * search: searchTerm,
895
- * batchSize: 10,
896
- * orderings: [{field: '_createdAt', direction: 'desc'}]
897
- * })
1195
+ * import {
1196
+ * useDocuments,
1197
+ * createDatasetHandle,
1198
+ * type DatasetHandle,
1199
+ * type DocumentHandle,
1200
+ * type SortOrderingItem
1201
+ * } from '@sanity/sdk-react'
1202
+ * import {Suspense} from 'react'
1203
+ *
1204
+ * // Define a component to display a single document (using useProjection for efficiency)
1205
+ * function MyDocumentComponent({doc}: {doc: DocumentHandle}) {
1206
+ * const {data} = useProjection<{title?: string}>({
1207
+ * ...doc, // Pass the full handle
1208
+ * projection: '{title}'
1209
+ * })
898
1210
  *
899
- * return (
900
- * <div>
901
- * Total documents: {count}
902
- * <ol>
903
- * {data.map((doc) => (
904
- * <li key={doc.documentId}>
905
- * <MyDocumentComponent doc={doc} />
906
- * </li>
1211
+ * return <>{data?.title || 'Untitled'}</>
1212
+ * }
1213
+ *
1214
+ * // Define props for the list component
1215
+ * interface DocumentListProps {
1216
+ * dataset: DatasetHandle
1217
+ * documentType: string
1218
+ * search?: string
1219
+ * }
1220
+ *
1221
+ * function DocumentList({dataset, documentType, search}: DocumentListProps) {
1222
+ * const { data, hasMore, isPending, loadMore, count } = useDocuments({
1223
+ * ...dataset,
1224
+ * documentType,
1225
+ * search,
1226
+ * batchSize: 10,
1227
+ * orderings: [{field: '_createdAt', direction: 'desc'}],
1228
+ * })
1229
+ *
1230
+ * return (
1231
+ * <div>
1232
+ * <p>Total documents: {count}</p>
1233
+ * <ol>
1234
+ * {data.map((docHandle) => (
1235
+ * <li key={docHandle.documentId}>
1236
+ * <Suspense fallback="Loading…">
1237
+ * <MyDocumentComponent docHandle={docHandle} />
1238
+ * </Suspense>
1239
+ * </li>
1240
+ * ))}
1241
+ * </ol>
1242
+ * {hasMore && (
1243
+ * <button onClick={loadMore}>
1244
+ * {isPending ? 'Loading...' : 'Load More'}
1245
+ * </button>
1246
+ * )}
1247
+ * </div>
1248
+ * )
1249
+ * }
1250
+ *
1251
+ * // Usage:
1252
+ * // const myDatasetHandle = createDatasetHandle({ projectId: 'p1', dataset: 'production' })
1253
+ * // <DocumentList dataset={myDatasetHandle} documentType="post" search="Sanity" />
1254
+ * ```
1255
+ *
1256
+ * @example Using `filter` and `params` options for narrowing a collection
1257
+ * ```tsx
1258
+ * import {useState} from 'react'
1259
+ * import {useDocuments} from '@sanity/sdk-react'
1260
+ *
1261
+ * export default function FilteredAuthors() {
1262
+ * const [max, setMax] = useState(2)
1263
+ * const {data} = useDocuments({
1264
+ * documentType: 'author',
1265
+ * filter: 'length(books) <= $max',
1266
+ * params: {max},
1267
+ * })
1268
+ *
1269
+ * return (
1270
+ * <>
1271
+ * <input
1272
+ * id="maxBooks"
1273
+ * type="number"
1274
+ * value={max}
1275
+ * onChange={e => setMax(e.currentTarget.value)}
1276
+ * />
1277
+ * {data.map(author => (
1278
+ * <Suspense key={author.documentId}>
1279
+ * <MyAuthorComponent documentHandle={author} />
1280
+ * </Suspense>
907
1281
  * ))}
908
- * </ol>
909
- * {hasMore && <button onClick={loadMore} disabled={isPending}>
910
- * {isPending ? 'Loading...' : 'Load More'}
911
- * </button>}
912
- * </div>
913
- * )
1282
+ * </>
1283
+ * )
1284
+ * }
914
1285
  * ```
915
1286
  */
916
- export declare function useDocuments({
1287
+ export declare function useDocuments<
1288
+ TDocumentType extends string = string,
1289
+ TDataset extends string = string,
1290
+ TProjectId extends string = string,
1291
+ >({
917
1292
  batchSize,
918
1293
  params,
919
1294
  search,
920
1295
  filter,
921
1296
  orderings,
1297
+ documentType,
922
1298
  ...options
923
- }: DocumentsOptions): DocumentsResponse
1299
+ }: DocumentsOptions<TDocumentType, TDataset, TProjectId>): DocumentsResponse<
1300
+ TDocumentType,
1301
+ TDataset,
1302
+ TProjectId
1303
+ >
924
1304
 
925
1305
  declare type UseDocumentSyncStatus = {
926
1306
  /**
@@ -930,20 +1310,32 @@ declare type UseDocumentSyncStatus = {
930
1310
  * @param doc - The document handle to get sync status for. If you pass a `DocumentHandle` with specified `projectId` and `dataset`,
931
1311
  * the document will be read from the specified Sanity project and dataset that is included in the handle. If no `projectId` or `dataset` is provided,
932
1312
  * the document will use the nearest instance from context.
933
- * @returns `true` if local changes are synced with remote, `false` if the changes are not synced, and `undefined` if the document is not found
934
- * @example Disable a Save button when there are no changes to sync
935
- * ```
936
- * const myDocumentHandle = { documentId: 'documentId', documentType: 'documentType', projectId: 'projectId', dataset: 'dataset' }
937
- * const documentSynced = useDocumentSyncStatus(myDocumentHandle)
1313
+ * @returns `true` if local changes are synced with remote, `false` if changes are pending. Note: Suspense handles loading states.
1314
+ * @example Show sync status indicator
1315
+ * ```tsx
1316
+ * import {useDocumentSyncStatus, createDocumentHandle, type DocumentHandle} from '@sanity/sdk-react'
938
1317
  *
939
- * return (
940
- * <button disabled={documentSynced}>
941
- * Save Changes
942
- * </button>
943
- * )
1318
+ * // Define props including the DocumentHandle type
1319
+ * interface SyncIndicatorProps {
1320
+ * doc: DocumentHandle
1321
+ * }
1322
+ *
1323
+ * function SyncIndicator({doc}: SyncIndicatorProps) {
1324
+ * const isSynced = useDocumentSyncStatus(doc)
1325
+ *
1326
+ * return (
1327
+ * <div className={`sync-status ${isSynced ? 'synced' : 'pending'}`}>
1328
+ * {isSynced ? '✓ Synced' : 'Saving changes...'}
1329
+ * </div>
1330
+ * )
1331
+ * }
1332
+ *
1333
+ * // Usage:
1334
+ * // const doc = createDocumentHandle({ documentId: 'doc1', documentType: 'myType' })
1335
+ * // <SyncIndicator doc={doc} />
944
1336
  * ```
945
1337
  */
946
- (doc: DocumentHandle): boolean | undefined
1338
+ (doc: DocumentHandle): boolean
947
1339
  }
948
1340
 
949
1341
  /**
@@ -953,142 +1345,65 @@ declare type UseDocumentSyncStatus = {
953
1345
  export declare const useDocumentSyncStatus: UseDocumentSyncStatus
954
1346
 
955
1347
  /**
956
- *
957
1348
  * @beta
1349
+ * Edit an entire document, relying on Typegen for the type.
958
1350
  *
959
- * ## useEditDocument(doc, path)
960
- * Edit a nested value within a document
961
- *
962
- * @category Documents
963
- * @param docHandle - The document to be edited, specified as a DocumentHandle
964
- * @param path - The path to the nested value to be edited
965
- * @returns A function to update the nested value. Accepts either a new value, or an updater function that exposes the previous value and returns a new value.
966
- * @example Update a document's name by providing the new value directly
967
- * ```tsx
968
- * const handle = {
969
- * documentId: 'movie-123',
970
- * documentType: 'movie',
971
- * projectId: 'abc123',
972
- * dataset: 'production'
973
- * }
974
- *
975
- * const name = useDocument(handle, 'name')
976
- * const editName = useEditDocument(handle, 'name')
977
- *
978
- * function handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
979
- * editName(event.target.value)
980
- * }
981
- *
982
- * return (
983
- * <input type='text' value={name} onChange={handleNameChange} />
984
- * )
985
- * ```
986
- *
987
- * @example Update a count on a document by providing an updater function
988
- * ```tsx
989
- * const handle = {
990
- * documentId: 'counter-123',
991
- * documentType: 'counter',
992
- * projectId: 'abc123',
993
- * dataset: 'production'
994
- * }
995
- *
996
- * const count = useDocument(handle, 'count')
997
- * const editCount = useEditDocument(handle, 'count')
998
- *
999
- * function incrementCount() {
1000
- * editCount(previousCount => previousCount + 1)
1001
- * }
1002
- *
1003
- * return (
1004
- * <>
1005
- * <button onClick={incrementCount}>
1006
- * Increment
1007
- * </button>
1008
- * Current count: {count}
1009
- * </>
1010
- * )
1011
- * ```
1351
+ * @param options - Document options including `documentId`, `documentType`, and optionally `projectId`/`dataset`.
1352
+ * @returns A stable function to update the document state. Accepts either the new document state or an updater function `(currentValue) => nextValue`.
1353
+ * Returns a promise resolving to the {@link ActionsResult}.
1012
1354
  */
1013
1355
  export declare function useEditDocument<
1014
- TDocument extends SanityDocument,
1015
- TPath extends JsonMatchPath<TDocument>,
1356
+ TDocumentType extends string = string,
1357
+ TDataset extends string = string,
1358
+ TProjectId extends string = string,
1016
1359
  >(
1017
- docHandle: DocumentHandle<TDocument>,
1018
- path: TPath,
1019
- ): (nextValue: Updater<JsonMatch<TDocument, TPath>>) => Promise<ActionsResult<TDocument>>
1360
+ options: DocumentOptions<undefined, TDocumentType, TDataset, TProjectId>,
1361
+ ): (
1362
+ nextValue: Updater<SanityDocumentResult<TDocumentType, TDataset, TProjectId>>,
1363
+ ) => Promise<ActionsResult<SanityDocumentResult<TDocumentType, TDataset, TProjectId>>>
1020
1364
 
1021
1365
  /**
1022
- *
1023
1366
  * @beta
1367
+ * Edit a specific path within a document, relying on Typegen for the type.
1024
1368
  *
1025
- * ## useEditDocument(doc)
1026
- * Edit an entire document
1027
- * @param docHandle - The document to be edited, specified as a DocumentHandle.
1028
- * The hook will automatically use the Sanity instance that matches the project and dataset specified in the handle.
1029
- * @returns A function to update the document state. Accepts either a new document state, or an updater function that exposes the previous document state and returns the new document state.
1030
- * @example
1031
- * ```tsx
1032
- * const myDocumentHandle = {
1033
- * documentId: 'product-123',
1034
- * documentType: 'product',
1035
- * projectId: 'abc123',
1036
- * dataset: 'production'
1037
- * }
1038
- *
1039
- * const myDocument = useDocument(myDocumentHandle)
1040
- * const { title, price } = myDocument ?? {}
1041
- *
1042
- * const editMyDocument = useEditDocument(myDocumentHandle)
1043
- *
1044
- * function handleFieldChange(e: React.ChangeEvent<HTMLInputElement>) {
1045
- * const {name, value} = e.currentTarget
1046
- * // Use an updater function to update the document state based on the previous state
1047
- * editMyDocument(previousDocument => ({
1048
- * ...previousDocument,
1049
- * [name]: value
1050
- * }))
1051
- * }
1369
+ * @param options - Document options including `documentId`, `documentType`, `path`, and optionally `projectId`/`dataset`.
1370
+ * @returns A stable function to update the value at the specified path. Accepts either the new value or an updater function `(currentValue) => nextValue`.
1371
+ * Returns a promise resolving to the {@link ActionsResult}.
1372
+ */
1373
+ export declare function useEditDocument<
1374
+ TPath extends string = string,
1375
+ TDocumentType extends string = string,
1376
+ TDataset extends string = string,
1377
+ TProjectId extends string = string,
1378
+ >(
1379
+ options: DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>,
1380
+ ): (
1381
+ nextValue: Updater<JsonMatch<SanityDocumentResult<TDocumentType, TDataset, TProjectId>, TPath>>,
1382
+ ) => Promise<ActionsResult<SanityDocumentResult<TDocumentType, TDataset, TProjectId>>>
1383
+
1384
+ /**
1385
+ * @beta
1386
+ * Edit an entire document with an explicit type `TData`.
1052
1387
  *
1053
- * function handleSaleChange(e: React.ChangeEvent<HTMLInputElement>) {
1054
- * const { checked } = e.currentTarget
1055
- * if (checked) {
1056
- * // Use an updater function to add a new salePrice field;
1057
- * // set it at a 20% discount off the normal price
1058
- * editMyDocument(previousDocument => ({
1059
- * ...previousDocument,
1060
- * salePrice: previousDocument.price * 0.8,
1061
- * }))
1062
- * } else {
1063
- * // Get the document state without the salePrice field
1064
- * const { salePrice, ...rest } = myDocument
1065
- * // Update the document state to remove the salePrice field
1066
- * editMyDocument(rest)
1067
- * }
1068
- * }
1388
+ * @param options - Document options including `documentId` and optionally `projectId`/`dataset`.
1389
+ * @returns A stable function to update the document state. Accepts either the new document state (`TData`) or an updater function `(currentValue: TData) => nextValue: TData`.
1390
+ * Returns a promise resolving to the {@link ActionsResult}.
1391
+ */
1392
+ export declare function useEditDocument<TData>(
1393
+ options: DocumentOptions<undefined>,
1394
+ ): (nextValue: Updater<TData>) => Promise<ActionsResult>
1395
+
1396
+ /**
1397
+ * @beta
1398
+ * Edit a specific path within a document with an explicit type `TData`.
1069
1399
  *
1070
- * return (
1071
- * <>
1072
- * <form onSubmit={e => e.preventDefault()}>
1073
- * <input name='title' type='text' value={title} onChange={handleFieldChange} />
1074
- * <input name='price' type='number' value={price} onChange={handleFieldChange} />
1075
- * <input
1076
- * name='salePrice'
1077
- * type='checkbox'
1078
- * checked={myDocument && 'salePrice' in myDocument}
1079
- * onChange={handleSaleChange}
1080
- * />
1081
- * </form>
1082
- * <pre><code>
1083
- * {JSON.stringify(myDocument, null, 2)}
1084
- * </code></pre>
1085
- * </>
1086
- * )
1087
- * ```
1400
+ * @param options - Document options including `documentId`, `path`, and optionally `projectId`/`dataset`.
1401
+ * @returns A stable function to update the value at the specified path. Accepts either the new value (`TData`) or an updater function `(currentValue: TData) => nextValue: TData`.
1402
+ * Returns a promise resolving to the {@link ActionsResult}.
1088
1403
  */
1089
- export declare function useEditDocument<TDocument extends SanityDocument>(
1090
- docHandle: DocumentHandle<TDocument>,
1091
- ): (nextValue: Updater<TDocument>) => Promise<ActionsResult<TDocument>>
1404
+ export declare function useEditDocument<TData>(
1405
+ options: DocumentOptions<string>,
1406
+ ): (nextValue: Updater<TData>) => Promise<ActionsResult>
1092
1407
 
1093
1408
  /**
1094
1409
  * @internal
@@ -1189,7 +1504,7 @@ export declare const useLogOut: () => () => Promise<void>
1189
1504
  *
1190
1505
  * @example
1191
1506
  * ```tsx
1192
- * function MyDocumentAction(props: DocumentActionProps) {
1507
+ * function FavoriteButton(props: DocumentActionProps) {
1193
1508
  * const {documentId, documentType} = props
1194
1509
  * const {favorite, unfavorite, isFavorited, isConnected} = useManageFavorite({
1195
1510
  * documentId,
@@ -1204,6 +1519,22 @@ export declare const useLogOut: () => () => Promise<void>
1204
1519
  * />
1205
1520
  * )
1206
1521
  * }
1522
+ *
1523
+ * // Wrap the component with Suspense since the hook may suspend
1524
+ * function MyDocumentAction(props: DocumentActionProps) {
1525
+ * return (
1526
+ * <Suspense
1527
+ * fallback={
1528
+ * <Button
1529
+ * text="Loading..."
1530
+ * disabled
1531
+ * />
1532
+ * }
1533
+ * >
1534
+ * <FavoriteButton {...props} />
1535
+ * </Suspense>
1536
+ * )
1537
+ * }
1207
1538
  * ```
1208
1539
  */
1209
1540
  export declare function useManageFavorite({
@@ -1273,54 +1604,141 @@ export declare function useNavigateToStudioDocument(
1273
1604
  * @beta
1274
1605
  * @category Documents
1275
1606
  * @param options - Configuration options for the paginated list
1276
- * @returns An object containing the current page of document handles, the loading and pagination state, and navigation functions
1607
+ * @returns An object containing the list of document handles, pagination details, and functions to navigate between pages
1277
1608
  *
1278
1609
  * @remarks
1279
1610
  * - The returned document handles include projectId and dataset information from the current Sanity instance
1280
1611
  * - This makes them ready to use with document operations and other document hooks
1281
1612
  * - The hook automatically uses the correct Sanity instance based on the projectId and dataset in the options
1282
1613
  *
1283
- * @example Basic usage
1614
+ * @example Paginated list of documents with navigation
1284
1615
  * ```tsx
1285
- * const {
1286
- * data,
1287
- * isPending,
1288
- * currentPage,
1289
- * totalPages,
1290
- * nextPage,
1291
- * previousPage,
1292
- * hasNextPage,
1293
- * hasPreviousPage
1294
- * } = usePaginatedDocuments({
1295
- * filter: '_type == "post"',
1296
- * search: searchTerm,
1297
- * pageSize: 10,
1298
- * orderings: [{field: '_createdAt', direction: 'desc'}]
1299
- * })
1616
+ * import {
1617
+ * usePaginatedDocuments,
1618
+ * createDatasetHandle,
1619
+ * type DatasetHandle,
1620
+ * type DocumentHandle,
1621
+ * type SortOrderingItem,
1622
+ * useProjection
1623
+ * } from '@sanity/sdk-react'
1624
+ * import {Suspense} from 'react'
1625
+ * import {ErrorBoundary} from 'react-error-boundary'
1626
+ *
1627
+ * // Define a component to display a single document row
1628
+ * function MyTableRowComponent({doc}: {doc: DocumentHandle}) {
1629
+ * const {data} = useProjection<{title?: string}>({
1630
+ * ...doc,
1631
+ * projection: '{title}',
1632
+ * })
1300
1633
  *
1301
- * return (
1302
- * <>
1303
- * <table>
1304
- * {data.map(doc => (
1305
- * <MyTableRowComponent key={doc.documentId} doc={doc} />
1306
- * ))}
1307
- * </table>
1308
- * {hasPreviousPage && <button onClick={previousPage}>Previous</button>}
1309
- * {currentPage} / {totalPages}
1310
- * {hasNextPage && <button onClick={nextPage}>Next</button>}
1311
- * </>
1312
- * )
1313
- * ```
1634
+ * return (
1635
+ * <tr>
1636
+ * <td>{data?.title ?? 'Untitled'}</td>
1637
+ * </tr>
1638
+ * )
1639
+ * }
1314
1640
  *
1641
+ * // Define props for the list component
1642
+ * interface PaginatedDocumentListProps {
1643
+ * documentType: string
1644
+ * dataset?: DatasetHandle
1645
+ * }
1646
+ *
1647
+ * function PaginatedDocumentList({documentType, dataset}: PaginatedDocumentListProps) {
1648
+ * const {
1649
+ * data,
1650
+ * isPending,
1651
+ * currentPage,
1652
+ * totalPages,
1653
+ * nextPage,
1654
+ * previousPage,
1655
+ * hasNextPage,
1656
+ * hasPreviousPage
1657
+ * } = usePaginatedDocuments({
1658
+ * ...dataset,
1659
+ * documentType,
1660
+ * pageSize: 10,
1661
+ * orderings: [{field: '_createdAt', direction: 'desc'}],
1662
+ * })
1663
+ *
1664
+ * return (
1665
+ * <div>
1666
+ * <table>
1667
+ * <thead>
1668
+ * <tr><th>Title</th></tr>
1669
+ * </thead>
1670
+ * <tbody>
1671
+ * {data.map(doc => (
1672
+ * <ErrorBoundary key={doc.documentId} fallback={<tr><td>Error loading document</td></tr>}>
1673
+ * <Suspense fallback={<tr><td>Loading...</td></tr>}>
1674
+ * <MyTableRowComponent doc={doc} />
1675
+ * </Suspense>
1676
+ * </ErrorBoundary>
1677
+ * ))}
1678
+ * </tbody>
1679
+ * </table>
1680
+ * <div style={{opacity: isPending ? 0.5 : 1}}>
1681
+ * <button onClick={previousPage} disabled={!hasPreviousPage || isPending}>Previous</button>
1682
+ * <span>Page {currentPage} / {totalPages}</span>
1683
+ * <button onClick={nextPage} disabled={!hasNextPage || isPending}>Next</button>
1684
+ * </div>
1685
+ * </div>
1686
+ * )
1687
+ * }
1688
+ *
1689
+ * // Usage:
1690
+ * // const myDatasetHandle = createDatasetHandle({ projectId: 'p1', dataset: 'production' })
1691
+ * // <PaginatedDocumentList dataset={myDatasetHandle} documentType="post" />
1692
+ * ```
1315
1693
  */
1316
- export declare function usePaginatedDocuments({
1694
+ export declare function usePaginatedDocuments<
1695
+ TDocumentType extends string = string,
1696
+ TDataset extends string = string,
1697
+ TProjectId extends string = string,
1698
+ >({
1699
+ documentType,
1317
1700
  filter,
1318
1701
  pageSize,
1319
1702
  params,
1320
1703
  orderings,
1321
1704
  search,
1322
1705
  ...options
1323
- }: PaginatedDocumentsOptions): PaginatedDocumentsResponse
1706
+ }: PaginatedDocumentsOptions<TDocumentType, TDataset, TProjectId>): PaginatedDocumentsResponse<
1707
+ TDocumentType,
1708
+ TDataset,
1709
+ TProjectId
1710
+ >
1711
+
1712
+ /**
1713
+ * @public
1714
+ */
1715
+ declare type UsePerspective = {
1716
+ (perspectiveHandle: PerspectiveHandle): string | string[]
1717
+ }
1718
+
1719
+ /**
1720
+ * @public
1721
+ * @function
1722
+ *
1723
+ * Returns a single or stack of perspectives for the given perspective handle,
1724
+ * which can then be used to correctly query the documents
1725
+ * via the `perspective` parameter in the client.
1726
+ *
1727
+ * @param perspectiveHandle - The perspective handle to get the perspective for.
1728
+ * @category Documents
1729
+ * @example
1730
+ * ```tsx
1731
+ * import {usePerspective, useQuery} from '@sanity/sdk-react'
1732
+
1733
+ * const perspective = usePerspective({perspective: 'rxg1346', projectId: 'abc123', dataset: 'production'})
1734
+ * const {data} = useQuery<Movie[]>('*[_type == "movie"]', {
1735
+ * perspective: perspective,
1736
+ * })
1737
+ * ```
1738
+ *
1739
+ * @returns The perspective for the given perspective handle.
1740
+ */
1741
+ export declare const usePerspective: UsePerspective
1324
1742
 
1325
1743
  /**
1326
1744
  * @beta
@@ -1424,83 +1842,160 @@ export declare const useProject: UseProject
1424
1842
  /**
1425
1843
  * @public
1426
1844
  *
1427
- * Returns the projection values of a document (specified via a `DocumentHandle`),
1428
- * based on the provided projection string. These values are live and will update in realtime.
1429
- * To reduce unnecessary network requests for resolving the projection values, an optional `ref` can be passed to the hook so that projection
1430
- * resolution will only occur if the `ref` is intersecting the current viewport.
1845
+ * Returns the projected values of a document based on the provided projection string.
1846
+ * These values are live and will update in realtime.
1847
+ * To optimize network requests, an optional `ref` can be passed to only resolve the projection
1848
+ * when the referenced element is intersecting the viewport.
1431
1849
  *
1432
1850
  * @category Documents
1433
- * @param options - The document handle for the document you want to project values from, the projection string, and an optional ref
1434
- * @returns The projection values for the given document and a boolean to indicate whether the resolution is pending
1851
+ * @remarks
1852
+ * This hook has multiple signatures allowing for fine-grained control over type inference:
1853
+ * - Using Typegen: Infers the return type based on the `documentType`, `dataset`, `projectId`, and `projection`.
1854
+ * - Using explicit type parameter: Allows specifying a custom return type `TData`.
1435
1855
  *
1436
- * @example Using a projection to render a preview of document
1437
- * ```
1438
- * // ProjectionComponent.jsx
1439
- * export default function ProjectionComponent({ document }) {
1440
- * const ref = useRef(null)
1441
- * const { data: { title, coverImage, authors }, isPending } = useProjection({
1442
- * ...document,
1856
+ * @param options - An object containing the `DocumentHandle` properties (`documentId`, `documentType`, etc.), the `projection` string, optional `params`, and an optional `ref`.
1857
+ * @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.
1858
+ */
1859
+ /**
1860
+ * @beta
1861
+ * Fetch a projection, relying on Typegen for the return type based on the handle and projection.
1862
+ *
1863
+ * @category Documents
1864
+ * @param options - Options including the document handle properties (`documentId`, `documentType`, etc.) and the `projection`.
1865
+ * @returns The projected data, typed based on Typegen.
1866
+ *
1867
+ * @example Using Typegen for a book preview
1868
+ * ```tsx
1869
+ * // ProjectionComponent.tsx
1870
+ * import {useProjection, type DocumentHandle} from '@sanity/sdk-react'
1871
+ * import {useRef} from 'react'
1872
+ * import {defineProjection} from 'groq'
1873
+ *
1874
+ * // Define props using DocumentHandle with the specific document type
1875
+ * type ProjectionComponentProps = {
1876
+ * doc: DocumentHandle<'book'> // Typegen knows 'book'
1877
+ * }
1878
+ *
1879
+ * // This is required for typegen to generate the correct return type
1880
+ * const myProjection = defineProjection(`{
1881
+ * title,
1882
+ * 'coverImage': cover.asset->url,
1883
+ * 'authors': array::join(authors[]->{'name': firstName + ' ' + lastName}.name, ', ')
1884
+ * }`)
1885
+ *
1886
+ * export default function ProjectionComponent({ doc }: ProjectionComponentProps) {
1887
+ * const ref = useRef(null) // Optional ref to track viewport intersection for lazy loading
1888
+ *
1889
+ * // Spread the doc handle into the options
1890
+ * // Typegen infers the return type based on 'book' and the projection
1891
+ * const { data } = useProjection({
1892
+ * ...doc, // Pass the handle properties
1443
1893
  * ref,
1444
- * projection: `{
1445
- * title,
1446
- * 'coverImage': cover.asset->url,
1447
- * 'authors': array::join(authors[]->{'name': firstName + ' ' + lastName + ' '}.name, ', ')
1448
- * }`,
1894
+ * projection: myProjection,
1449
1895
  * })
1450
1896
  *
1897
+ * // Suspense handles initial load, check for data existence after
1451
1898
  * return (
1452
- * <article ref={ref} style={{ opacity: isPending ? 0.5 : 1}}>
1453
- * <h2>{title}</h2>
1454
- * <img src={coverImage} alt={title} />
1455
- * <p>{authors}</p>
1899
+ * <article ref={ref}>
1900
+ * <h2>{data.title ?? 'Untitled'}</h2>
1901
+ * {data.coverImage && <img src={data.coverImage} alt={data.title} />}
1902
+ * <p>{data.authors ?? 'Unknown authors'}</p>
1456
1903
  * </article>
1457
1904
  * )
1458
1905
  * }
1459
- * ```
1460
1906
  *
1461
- * @example Combining with useDocuments to render a collection with specific fields
1907
+ * // Usage:
1908
+ * // import {createDocumentHandle} from '@sanity/sdk-react'
1909
+ * // const myDocHandle = createDocumentHandle({ documentId: 'book123', documentType: 'book' })
1910
+ * // <Suspense fallback='Loading preview...'>
1911
+ * // <ProjectionComponent doc={myDocHandle} />
1912
+ * // </Suspense>
1462
1913
  * ```
1463
- * // DocumentList.jsx
1464
- * const { data } = useDocuments({ filter: '_type == "article"' })
1465
- * return (
1466
- * <div>
1467
- * <h1>Books</h1>
1468
- * <ul>
1469
- * {data.map(book => (
1470
- * <li key={book._id}>
1471
- * <Suspense fallback='Loading…'>
1472
- * <ProjectionComponent
1473
- * document={book}
1474
- * />
1475
- * </Suspense>
1476
- * </li>
1477
- * ))}
1478
- * </ul>
1479
- * </div>
1480
- * )
1914
+ */
1915
+ export declare function useProjection<
1916
+ TProjection extends ValidProjection = ValidProjection,
1917
+ TDocumentType extends string = string,
1918
+ TDataset extends string = string,
1919
+ TProjectId extends string = string,
1920
+ >(
1921
+ options: UseProjectionOptions<TProjection, TDocumentType, TDataset, TProjectId>,
1922
+ ): UseProjectionResults<SanityProjectionResult<TProjection, TDocumentType, TDataset, TProjectId>>
1923
+
1924
+ /**
1925
+ * @beta
1926
+ * Fetch a projection with an explicitly defined return type `TData`.
1927
+ *
1928
+ * @param options - Options including the document handle properties (`documentId`, etc.) and the `projection`.
1929
+ * @returns The projected data, cast to the explicit type `TData`.
1930
+ *
1931
+ * @example Explicitly typing the projection result
1932
+ * ```tsx
1933
+ * import {useProjection, type DocumentHandle} from '@sanity/sdk-react'
1934
+ * import {useRef} from 'react'
1935
+ *
1936
+ * interface SimpleBookPreview {
1937
+ * title?: string;
1938
+ * authorName?: string;
1939
+ * }
1940
+ *
1941
+ * type BookPreviewProps = {
1942
+ * doc: DocumentHandle
1943
+ * }
1944
+ *
1945
+ * function BookPreview({ doc }: BookPreviewProps) {
1946
+ * const ref = useRef(null)
1947
+ * const { data } = useProjection<SimpleBookPreview>({
1948
+ * ...doc,
1949
+ * ref,
1950
+ * projection: `{ title, 'authorName': author->name }`
1951
+ * })
1952
+ *
1953
+ * return (
1954
+ * <div ref={ref}>
1955
+ * <h3>{data.title ?? 'No Title'}</h3>
1956
+ * <p>By: {data.authorName ?? 'Unknown'}</p>
1957
+ * </div>
1958
+ * )
1959
+ * }
1960
+ *
1961
+ * // Usage:
1962
+ * // import {createDocumentHandle} from '@sanity/sdk-react'
1963
+ * // const doc = createDocumentHandle({ documentId: 'abc', documentType: 'book' })
1964
+ * // <Suspense fallback='Loading...'>
1965
+ * // <BookPreview doc={doc} />
1966
+ * // </Suspense>
1481
1967
  * ```
1482
1968
  */
1483
- export declare function useProjection<TData extends object>({
1484
- ref,
1485
- projection,
1486
- ...docHandle
1487
- }: UseProjectionOptions): UseProjectionResults<TData>
1969
+ export declare function useProjection<TData extends object>(
1970
+ options: UseProjectionOptions,
1971
+ ): UseProjectionResults<TData>
1488
1972
 
1489
1973
  /**
1490
1974
  * @public
1491
1975
  * @category Types
1492
1976
  */
1493
- export declare interface UseProjectionOptions extends DocumentHandle {
1977
+ export declare interface UseProjectionOptions<
1978
+ TProjection extends ValidProjection = ValidProjection,
1979
+ TDocumentType extends string = string,
1980
+ TDataset extends string = string,
1981
+ TProjectId extends string = string,
1982
+ > extends DocumentHandle<TDocumentType, TDataset, TProjectId> {
1983
+ /** The GROQ projection string */
1984
+ projection: TProjection
1985
+ /** Optional parameters for the projection query */
1986
+ params?: Record<string, unknown>
1987
+ /** Optional ref to track viewport intersection for lazy loading */
1494
1988
  ref?: React.RefObject<unknown>
1495
- projection: ValidProjection
1496
1989
  }
1497
1990
 
1498
1991
  /**
1499
1992
  * @public
1500
1993
  * @category Types
1501
1994
  */
1502
- export declare interface UseProjectionResults<TData extends object> {
1995
+ export declare interface UseProjectionResults<TData> {
1996
+ /** The projected data */
1503
1997
  data: TData
1998
+ /** True if the projection is currently being resolved */
1504
1999
  isPending: boolean
1505
2000
  }
1506
2001
 
@@ -1534,63 +2029,105 @@ declare type UseProjects = {
1534
2029
  export declare const useProjects: UseProjects
1535
2030
 
1536
2031
  /**
1537
- * Executes GROQ queries against a Sanity dataset.
2032
+ * @beta
2033
+ * Executes a GROQ query, inferring the result type from the query string and options.
2034
+ * Leverages Sanity Typegen if configured for enhanced type safety.
1538
2035
  *
1539
- * This hook provides a convenient way to fetch and subscribe to real-time updates
1540
- * for your Sanity content. Changes made to the dataset's content will trigger
1541
- * automatic updates.
2036
+ * @param options - Configuration for the query, including `query`, optional `params`, `projectId`, `dataset`, etc.
2037
+ * @returns An object containing `data` (typed based on the query) and `isPending` (for transitions).
1542
2038
  *
1543
- * @remarks
1544
- * The returned `isPending` flag indicates when a React transition is in progress,
1545
- * which can be used to show loading states for query changes.
2039
+ * @example Basic usage (Inferred Type)
2040
+ * ```tsx
2041
+ * import {useQuery} from '@sanity/sdk-react'
2042
+ * import {defineQuery} from 'groq'
1546
2043
  *
1547
- * @beta
1548
- * @category GROQ
1549
- * @param query - GROQ query string to execute
1550
- * @param options - Optional configuration for the query, including projectId and dataset
1551
- * @returns Object containing the query result and a pending state flag
2044
+ * const myQuery = defineQuery(`*[_type == "movie"]{_id, title}`)
1552
2045
  *
1553
- * @example Basic usage
1554
- * ```tsx
1555
- * const {data, isPending} = useQuery<Movie[]>('*[_type == "movie"]')
1556
- * ```
2046
+ * function MovieList() {
2047
+ * // Typegen infers the return type for data
2048
+ * const {data} = useQuery({ query: myQuery })
1557
2049
  *
1558
- * @example Using parameters
1559
- * ```tsx
1560
- * // With parameters
1561
- * const {data} = useQuery<Movie>('*[_type == "movie" && _id == $id][0]', {
1562
- * params: { id: 'movie-123' }
1563
- * })
2050
+ * return (
2051
+ * <div>
2052
+ * <h2>Movies</h2>
2053
+ * <ul>
2054
+ * {data.map(movie => <li key={movie._id}>{movie.title}</li>)}
2055
+ * </ul>
2056
+ * </div>
2057
+ * )
2058
+ * }
2059
+ * // Suspense boundary should wrap <MovieList /> for initial load
1564
2060
  * ```
1565
2061
  *
1566
- * @example Query from a specific project/dataset
2062
+ * @example Using parameters (Inferred Type)
1567
2063
  * ```tsx
1568
- * // Specify which project and dataset to query
1569
- * const {data} = useQuery<Movie[]>('*[_type == "movie"]', {
1570
- * projectId: 'abc123',
1571
- * dataset: 'production'
1572
- * })
2064
+ * import {useQuery} from '@sanity/sdk-react'
2065
+ * import {defineQuery} from 'groq'
2066
+ *
2067
+ * const myQuery = defineQuery(`*[_type == "movie" && _id == $id][0]`)
2068
+ *
2069
+ * function MovieDetails({movieId}: {movieId: string}) {
2070
+ * // Typegen infers the return type based on query and params
2071
+ * const {data, isPending} = useQuery({
2072
+ * query: myQuery,
2073
+ * params: { id: movieId }
2074
+ * })
2075
+ *
2076
+ * return (
2077
+ * // utilize `isPending` to signal to users that new data is coming in
2078
+ * // (e.g. the `movieId` changed and we're loading in the new one)
2079
+ * <div style={{ opacity: isPending ? 0.5 : 1 }}>
2080
+ * {data ? <h1>{data.title}</h1> : <p>Movie not found</p>}
2081
+ * </div>
2082
+ * )
2083
+ * }
1573
2084
  * ```
2085
+ */
2086
+ export declare function useQuery<
2087
+ TQuery extends string = string,
2088
+ TDataset extends string = string,
2089
+ TProjectId extends string = string,
2090
+ >(
2091
+ options: QueryOptions<TQuery, TDataset, TProjectId>,
2092
+ ): {
2093
+ /** The query result, typed based on the GROQ query string */
2094
+ data: SanityQueryResult<TQuery, TDataset, TProjectId>
2095
+ /** True if a query transition is in progress */
2096
+ isPending: boolean
2097
+ }
2098
+
2099
+ /**
2100
+ * @beta
2101
+ * Executes a GROQ query with an explicitly provided result type `TData`.
1574
2102
  *
1575
- * @example With a loading state for transitions
2103
+ * @param options - Configuration for the query, including `query`, optional `params`, `projectId`, `dataset`, etc.
2104
+ * @returns An object containing `data` (cast to `TData`) and `isPending` (indicates whether a query resolution is pending; note that Suspense handles initial loading states). *
2105
+ * @example Manually typed query result
1576
2106
  * ```tsx
1577
- * const {data, isPending} = useQuery<Movie[]>('*[_type == "movie"]')
1578
- * return (
1579
- * <div>
1580
- * {isPending && <div>Updating...</div>}
1581
- * <ul>
1582
- * {data.map(movie => <li key={movie._id}>{movie.title}</li>)}
1583
- * </ul>
1584
- * </div>
1585
- * )
1586
- * ```
2107
+ * import {useQuery} from '@sanity/sdk-react'
2108
+ *
2109
+ * interface CustomMovieTitle {
2110
+ * movieTitle?: string
2111
+ * }
2112
+ *
2113
+ * function FirstMovieTitle() {
2114
+ * // Provide the explicit type TData
2115
+ * const {data, isPending} = useQuery<CustomMovieTitle>({
2116
+ * query: '*[_type == "movie"][0]{ "movieTitle": title }'
2117
+ * })
1587
2118
  *
2119
+ * return (
2120
+ * <h1 style={{ opacity: isPending ? 0.5 : 1 }}>
2121
+ * {data?.movieTitle ?? 'No title found'}
2122
+ * </h1>
2123
+ * )
2124
+ * }
2125
+ * ```
1588
2126
  */
1589
- export declare function useQuery<T>(
1590
- query: string,
1591
- options?: QueryOptions,
1592
- ): {
1593
- data: T
2127
+ export declare function useQuery<TData>(options: QueryOptions): {
2128
+ /** The query result, cast to the provided type TData */
2129
+ data: TData
2130
+ /** True if another query is resolving in the background (suspense handles the initial loading state) */
1594
2131
  isPending: boolean
1595
2132
  }
1596
2133
 
@@ -1765,6 +2302,31 @@ export declare function useStudioWorkspacesByProjectIdDataset(): StudioWorkspace
1765
2302
  */
1766
2303
  export declare function useUsers(options?: GetUsersOptions): UsersResult
1767
2304
 
2305
+ /**
2306
+ * Hook that verifies the current projects belongs to the organization ID specified in the dashboard context.
2307
+ *
2308
+ * @public
2309
+ * @param disabled - When true, disables verification and skips project verification API calls
2310
+ * @returns Error message if the project doesn't match the organization ID, or null if all match or verification isn't needed
2311
+ * @category Projects
2312
+ * @example
2313
+ * ```tsx
2314
+ * function OrgVerifier() {
2315
+ * const error = useVerifyOrgProjects()
2316
+ *
2317
+ * if (error) {
2318
+ * return <div className="error">{error}</div>
2319
+ * }
2320
+ *
2321
+ * return <div>Organization projects verified!</div>
2322
+ * }
2323
+ * ```
2324
+ */
2325
+ export declare function useVerifyOrgProjects(
2326
+ disabled?: boolean,
2327
+ projectIds?: string[],
2328
+ ): string | null
2329
+
1768
2330
  /**
1769
2331
  * @internal
1770
2332
  * Hook to wrap a Comlink node in a React hook.