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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +125 -63
  2. package/dist/index.d.ts +381 -571
  3. package/dist/index.js +450 -366
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -8
  6. package/src/_exports/index.ts +4 -0
  7. package/src/_exports/sdk-react.ts +16 -0
  8. package/src/components/SDKProvider.test.tsx +23 -58
  9. package/src/components/SDKProvider.tsx +38 -30
  10. package/src/components/SanityApp.test.tsx +12 -68
  11. package/src/components/SanityApp.tsx +88 -65
  12. package/src/components/auth/AuthBoundary.test.tsx +11 -26
  13. package/src/components/auth/LoginError.test.tsx +5 -0
  14. package/src/components/auth/LoginError.tsx +23 -2
  15. package/src/config/handles.ts +53 -0
  16. package/src/context/ComlinkTokenRefresh.test.tsx +27 -10
  17. package/src/context/DefaultResourceContext.ts +10 -0
  18. package/src/context/PerspectiveContext.ts +12 -0
  19. package/src/context/ResourceProvider.test.tsx +99 -19
  20. package/src/context/ResourceProvider.tsx +103 -37
  21. package/src/context/ResourcesContext.tsx +7 -0
  22. package/src/context/SDKStudioContext.test.tsx +33 -28
  23. package/src/context/SDKStudioContext.ts +6 -0
  24. package/src/context/renderSanityApp.test.tsx +49 -151
  25. package/src/context/renderSanityApp.tsx +8 -12
  26. package/src/hooks/agent/agentActions.test.tsx +1 -1
  27. package/src/hooks/agent/agentActions.ts +56 -19
  28. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +8 -2
  29. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +32 -8
  30. package/src/hooks/client/useClient.test.tsx +4 -1
  31. package/src/hooks/client/useClient.ts +0 -1
  32. package/src/hooks/context/useDefaultResource.test.tsx +25 -0
  33. package/src/hooks/context/useDefaultResource.ts +30 -0
  34. package/src/hooks/context/useSanityInstance.test.tsx +2 -140
  35. package/src/hooks/context/useSanityInstance.ts +9 -53
  36. package/src/hooks/dashboard/useDispatchIntent.test.ts +24 -15
  37. package/src/hooks/dashboard/useDispatchIntent.ts +7 -7
  38. package/src/hooks/dashboard/useManageFavorite.test.tsx +34 -94
  39. package/src/hooks/dashboard/useManageFavorite.ts +16 -10
  40. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +7 -5
  41. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +6 -2
  42. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +2 -0
  43. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
  44. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +17 -38
  45. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +12 -19
  46. package/src/hooks/datasets/useDatasets.test.ts +8 -22
  47. package/src/hooks/datasets/useDatasets.ts +8 -16
  48. package/src/hooks/document/useApplyDocumentActions.test.ts +98 -52
  49. package/src/hooks/document/useApplyDocumentActions.ts +35 -37
  50. package/src/hooks/document/useDocument.test.tsx +8 -37
  51. package/src/hooks/document/useDocument.ts +78 -129
  52. package/src/hooks/document/useDocumentEvent.test.tsx +7 -19
  53. package/src/hooks/document/useDocumentEvent.ts +21 -19
  54. package/src/hooks/document/useDocumentPermissions.test.tsx +75 -84
  55. package/src/hooks/document/useDocumentPermissions.ts +41 -28
  56. package/src/hooks/document/useDocumentSyncStatus.test.ts +13 -3
  57. package/src/hooks/document/useDocumentSyncStatus.ts +19 -14
  58. package/src/hooks/document/useEditDocument.test.tsx +28 -70
  59. package/src/hooks/document/useEditDocument.ts +29 -149
  60. package/src/hooks/documents/useDocuments.test.tsx +44 -64
  61. package/src/hooks/documents/useDocuments.ts +19 -25
  62. package/src/hooks/helpers/createCallbackHook.test.tsx +19 -13
  63. package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
  64. package/src/hooks/helpers/createStateSourceHook.tsx +2 -4
  65. package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +65 -0
  66. package/src/hooks/helpers/useNormalizedResourceOptions.ts +127 -0
  67. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +27 -34
  68. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +19 -20
  69. package/src/hooks/presence/usePresence.test.tsx +71 -9
  70. package/src/hooks/presence/usePresence.ts +28 -3
  71. package/src/hooks/preview/useDocumentPreview.test.tsx +85 -193
  72. package/src/hooks/preview/useDocumentPreview.tsx +42 -62
  73. package/src/hooks/projection/useDocumentProjection.test.tsx +9 -37
  74. package/src/hooks/projection/useDocumentProjection.ts +9 -82
  75. package/src/hooks/projects/useProject.test.ts +1 -2
  76. package/src/hooks/projects/useProject.ts +7 -8
  77. package/src/hooks/query/useQuery.test.tsx +5 -6
  78. package/src/hooks/query/useQuery.ts +12 -91
  79. package/src/hooks/releases/useActiveReleases.test.tsx +2 -2
  80. package/src/hooks/releases/useActiveReleases.ts +25 -13
  81. package/src/hooks/releases/usePerspective.test.tsx +9 -17
  82. package/src/hooks/releases/usePerspective.ts +29 -18
  83. package/src/hooks/users/useUser.test.tsx +9 -3
  84. package/src/hooks/users/useUser.ts +1 -1
  85. package/src/hooks/users/useUsers.test.tsx +5 -2
  86. package/src/hooks/users/useUsers.ts +1 -1
  87. package/src/context/SourcesContext.tsx +0 -7
  88. package/src/hooks/helpers/useNormalizedSourceOptions.ts +0 -85
package/README.md CHANGED
@@ -5,7 +5,9 @@
5
5
  <h1 align="center">Sanity App SDK (React)</h1>
6
6
  </p>
7
7
 
8
- React hooks for creating Sanity applications. Live by default, optimistic updates, multi-project support.
8
+ React hooks for creating Sanity applications. Live by default, optimistic updates, multi-resource support.
9
+
10
+ > **Requires React 19** — `react` and `react-dom` `^19.2.0` are the minimum peer dependencies.
9
11
 
10
12
  ---
11
13
 
@@ -30,22 +32,31 @@ Opens at `https://www.sanity.io/welcome?dev=http%3A%2F%2Flocalhost%3A3333`, prox
30
32
  ### 2. Project configuration
31
33
 
32
34
  ```tsx
33
- import {SanityApp, type SanityConfig} from '@sanity/sdk-react'
34
-
35
- const config: SanityConfig[] = [
36
- {projectId: 'abc123', dataset: 'production'},
37
- {projectId: 'def456', dataset: 'production'}, // multi-project support
38
- ]
35
+ import {SanityApp} from '@sanity/sdk-react'
39
36
 
40
37
  export function App() {
41
38
  return (
42
- <SanityApp config={config} fallback={<div>Loading...</div>}>
39
+ <SanityApp
40
+ resources={{
41
+ 'default': {projectId: 'abc123', dataset: 'production'},
42
+ 'second-project': {projectId: 'def456', dataset: 'production'},
43
+ }}
44
+ fallback={<div>Loading...</div>}
45
+ >
43
46
  <YourApp />
44
47
  </SanityApp>
45
48
  )
46
49
  }
47
50
  ```
48
51
 
52
+ In Sanity, a **resource** identifies where your data lives. It can be one of:
53
+
54
+ - a project and dataset pair (`{ projectId, dataset }`)
55
+ - a media library (`{ mediaLibraryId }`)
56
+ - or a canvas (`{ canvasId }`)
57
+
58
+ The `resources` prop is a map of named resources. Each resource tells the SDK where to read and write data. The resource keyed `"default"` is used automatically when no explicit resource is specified in a hook.
59
+
49
60
  **Auth is automatic** — Dashboard injects an auth token via iframe. No custom login flow is needed for your application.
50
61
 
51
62
  ---
@@ -60,11 +71,14 @@ Document handles are a core concept for apps built with the App SDK. Document ha
60
71
  type DocumentHandle = {
61
72
  documentId: string
62
73
  documentType: string
63
- projectId?: string // optional if using the default projectId or inside a ResourceProvider
64
- dataset?: string // optional if using the default dataset or inside a ResourceProvider
74
+ resource: DocumentResource // e.g. { projectId, dataset }, { mediaLibraryId }, or { canvasId }
65
75
  }
66
76
  ```
67
77
 
78
+ The `resource` field identifies where the document lives. When you fetch document handles from hooks like `useDocuments`, the `resource` is automatically populated from the current context.
79
+
80
+ Most hooks also accept `resourceName` to target a specific named resource declared in the `resources` prop on `<SanityApp>`, or a `resource` object directly. When neither is provided, the `"default"` resource is used.
81
+
68
82
  **Best practice:** Fetch document handles first → pass them to child components → fetch individual document content from child components.
69
83
 
70
84
  ---
@@ -123,7 +137,7 @@ import {
123
137
  unpublishDocument,
124
138
  deleteDocument,
125
139
  createDocument,
126
- discardDraft,
140
+ discardDocument,
127
141
  } from '@sanity/sdk-react'
128
142
 
129
143
  const apply = useApplyDocumentActions()
@@ -179,7 +193,7 @@ The `useApplyDocumentActions` hook is used to perform document lifecycle operati
179
193
  | `publishDocument` | Publish a draft (copy draft → published) |
180
194
  | `unpublishDocument` | Unpublish (delete published, keep draft) |
181
195
  | `deleteDocument` | Delete document entirely (draft and published) |
182
- | `discardDraft` | Discard draft changes, revert to published |
196
+ | `discardDocument` | Discard draft changes, revert to published |
183
197
 
184
198
  #### Creating Documents
185
199
 
@@ -320,7 +334,7 @@ The SDK handles updating the document state automatically:
320
334
  - `useDocument()` returns draft if exists, else published
321
335
  - `useEditDocument()` creates draft on first edit (automatic)
322
336
  - `publishDocument()` copies draft → published, deletes draft
323
- - `discardDraft()` deletes draft, reverts to published
337
+ - `discardDocument()` deletes draft, reverts to published
324
338
 
325
339
  #### LiveEdit Documents
326
340
 
@@ -330,6 +344,7 @@ For documents that don't need the draft/published workflow (such as settings, co
330
344
  const settingsHandle: DocumentHandle = {
331
345
  documentId: 'site-settings',
332
346
  documentType: 'settings',
347
+ resource: {projectId: 'abc123', dataset: 'production'},
333
348
  liveEdit: true, // Edits apply directly without creating a draft
334
349
  }
335
350
 
@@ -341,7 +356,7 @@ const editSettings = useEditDocument(settingsHandle)
341
356
 
342
357
  - Drafts will not be created when the document is edited
343
358
  - Edits will be applied directly to the published document
344
- - `publishDocument()`, `unpublishDocument()`, and `discardDraft()` actions cannot be used (since liveEdit documents are always published and do not have drafts)
359
+ - `publishDocument()`, `unpublishDocument()`, and `discardDocument()` actions cannot be used (since liveEdit documents are always published and do not have drafts)
345
360
 
346
361
  For more details, see the [Sanity documentation on liveEdit documents](https://www.sanity.io/docs/content-lake/drafts).
347
362
 
@@ -361,32 +376,80 @@ Any mutation to a subscribed document (even fields you don't display) will trigg
361
376
 
362
377
  ---
363
378
 
364
- ### Multi-Project Access
379
+ ### Multi-Resource Access
380
+
381
+ If your app only uses a single project and dataset, the `"default"` resource handles everything. When you need to pull data from additional projects, datasets, media libraries, or canvases, you can specify additional resources as needed. There are three main approaches to providing additional resources:
382
+
383
+ #### Approach 1: Use Named Resources (recommended)
384
+
385
+ Register all your resources in the `resources` prop on `<SanityApp>` and reference them by name with `resourceName`:
386
+
387
+ ```tsx
388
+ import {SanityApp} from '@sanity/sdk-react'
389
+
390
+ export function App() {
391
+ return (
392
+ <SanityApp
393
+ resources={{
394
+ default: {projectId: 'project-a', dataset: 'production'},
395
+ staging: {projectId: 'project-a', dataset: 'staging'},
396
+ media: {mediaLibraryId: 'my-media-library'},
397
+ }}
398
+ fallback={<div>Loading...</div>}
399
+ >
400
+ <MyApp />
401
+ </SanityApp>
402
+ )
403
+ }
404
+ ```
405
+
406
+ Then reference them in hooks:
407
+
408
+ ```tsx
409
+ import {useDocument, useQuery} from '@sanity/sdk-react'
410
+
411
+ function StagingPreview({documentId}: {documentId: string}) {
412
+ const {data} = useDocument({
413
+ documentId,
414
+ documentType: 'article',
415
+ resourceName: 'staging',
416
+ })
417
+ return <pre>{JSON.stringify(data, null, 2)}</pre>
418
+ }
365
419
 
366
- The SDK supports accessing documents from multiple projects and datasets simultaneously. There are two main approaches:
420
+ function MediaAssets() {
421
+ const {data} = useQuery({
422
+ query: '*[_type == "sanity.asset"][0...10]',
423
+ resourceName: 'media',
424
+ })
425
+ return (
426
+ <ul>
427
+ {data?.map((asset) => (
428
+ <li key={asset._id}>{asset.originalFilename}</li>
429
+ ))}
430
+ </ul>
431
+ )
432
+ }
433
+ ```
367
434
 
368
- #### Approach 1: Specify Project/Dataset Directly in the Handle
435
+ #### Approach 2: Pass a Resource Object Directly
369
436
 
370
- Pass `projectId` and `dataset` directly in document handles to fetch data from specific projects (note that any `projectId` and `dataset` pair you pass must be defined in your application’s array of [SanityConfig objects](https://www.sanity.io/docs/app-sdk/sdk-configuration#d95b8773097c)):
437
+ If you only need to access a resource in one or two places within your app, you can pass it inline via the `resource` option:
371
438
 
372
439
  ```tsx
373
440
  import {useDocument} from '@sanity/sdk-react'
374
441
 
375
442
  function MultiProjectComponent() {
376
- // Fetch from Project A
377
443
  const {data: productA} = useDocument({
378
444
  documentId: 'product-123',
379
445
  documentType: 'product',
380
- projectId: 'project-a',
381
- dataset: 'production',
446
+ resource: {projectId: 'project-a', dataset: 'production'},
382
447
  })
383
448
 
384
- // Fetch from Project B
385
449
  const {data: productB} = useDocument({
386
450
  documentId: 'product-456',
387
451
  documentType: 'product',
388
- projectId: 'project-b',
389
- dataset: 'staging',
452
+ resource: {projectId: 'project-b', dataset: 'staging'},
390
453
  })
391
454
 
392
455
  return (
@@ -398,46 +461,41 @@ function MultiProjectComponent() {
398
461
  }
399
462
  ```
400
463
 
401
- #### Approach 2: Use ResourceProvider to Set Context
464
+ #### Approach 3: Use ResourceProvider to Set Context
402
465
 
403
- Wrap components in `ResourceProvider` to set default project/dataset values for all child components:
466
+ If you need to access certain resources within multiple sibling components, wrap those components in `ResourceProvider` and set a default resource for all its child components:
404
467
 
405
468
  ```tsx
406
- // App.tsx
407
- import {ResourceProvider, useDocument, useSanityInstance} from '@sanity/sdk-react'
469
+ import {ResourceProvider, useDocument} from '@sanity/sdk-react'
408
470
 
409
471
  function ProductCard({productId}: {productId: string}) {
410
- // Get the current project/dataset from context
411
- const {config} = useSanityInstance()
412
-
413
- // No need to specify projectId/dataset - inherited from ResourceProvider
472
+ // No need to specify a resource - inherited from ResourceProvider
414
473
  const {data: product} = useDocument({
415
474
  documentId: productId,
416
475
  documentType: 'product',
417
476
  })
418
477
 
419
- return (
420
- <div>
421
- <h3>{product?.title}</h3>
422
- <p>
423
- From: {config.projectId}.{config.dataset}
424
- </p>
425
- </div>
426
- )
478
+ return <h3>{product?.title}</h3>
427
479
  }
428
480
 
429
481
  export function MultiProjectApp() {
430
482
  return (
431
483
  <div>
432
484
  {/* Products from Project A */}
433
- <ResourceProvider projectId="project-a" dataset="production" fallback={<div>Loading...</div>}>
485
+ <ResourceProvider
486
+ resource={{projectId: 'project-a', dataset: 'production'}}
487
+ fallback={<div>Loading...</div>}
488
+ >
434
489
  <h2>Project A Products</h2>
435
490
  <ProductCard productId="product-123" />
436
491
  <ProductCard productId="product-456" />
437
492
  </ResourceProvider>
438
493
 
439
494
  {/* Products from Project B */}
440
- <ResourceProvider projectId="project-b" dataset="staging" fallback={<div>Loading...</div>}>
495
+ <ResourceProvider
496
+ resource={{projectId: 'project-b', dataset: 'staging'}}
497
+ fallback={<div>Loading...</div>}
498
+ >
441
499
  <h2>Project B Products</h2>
442
500
  <ProductCard productId="product-789" />
443
501
  </ResourceProvider>
@@ -448,17 +506,16 @@ export function MultiProjectApp() {
448
506
 
449
507
  **Key Points:**
450
508
 
451
- - When using hooks that take document handles as arguments (such useDocument, useEditDocument, useQuery, etc.), the document handles’ `projectId` and `dataset` values can be explicitly set to fetch documents from arbitrary projects and datasets
452
- - The ResourceProvider component is used to create a project ID and dataset context that child components will inherit from; this can negate the need to specify the project ID and dataset values for document handles in hooks called by child components
453
- - Use `useSanityInstance()` to access the context configuration for the current component: `const {config} = useSanityInstance()`
454
- - You can nest ResourceProvider components to create component trees with different project/dataset configurations — but be aware that, when the project ID and dataset values for document handles are _not_ specified, the project ID and dataset from the closest ResourceProvider context will be used
455
- - Regardless of the approach you use, the project IDs and dataset names you reference (whether in document handles or ResourceProviders) must be enumerated in your application’s [SanityConfig objects](https://www.sanity.io/docs/app-sdk/sdk-configuration#d95b8773097c)
509
+ - The `"default"` resource is used automatically when no `resourceName`, `resource`, or `ResourceProvider` context is present
510
+ - Named resources (`resourceName`) are the recommended pattern for apps that work with multiple data sources
511
+ - `ResourceProvider` sets context for an entire subtree — hooks inside it inherit the resource without needing to specify it
512
+ - You can nest `ResourceProvider` components; the closest provider wins when no explicit resource is given
456
513
 
457
514
  ---
458
515
 
459
516
  ### Using the SDK inside Sanity Studio
460
517
 
461
- The SDK can be embedded directly inside a Sanity Studio with zero manual configuration. Sanity Studio provides `SDKStudioContext` automatically, so `SanityApp` derives `projectId`, `dataset`, and auth from the Studio's workspace without any setup.
518
+ The SDK can be embedded directly inside a Sanity Studio with zero manual configuration. Sanity Studio provides `SDKStudioContext` automatically, so `SanityApp` derives its resource configuration and auth from the Studio's workspace without any setup.
462
519
 
463
520
  #### Zero-config setup (recommended)
464
521
 
@@ -492,11 +549,13 @@ function StudioSDKWrapper({children}) {
492
549
 
493
550
  #### Explicit config takes precedence
494
551
 
495
- If you pass a `config` prop to `SanityApp`, this config will take precedence over any workspace config picked up by `SDKStudioContext`:
552
+ If you pass `resources` (data sources) or `config` (auth, studio, and perspective settings) props to `SanityApp`, they take precedence over any workspace config picked up by `SDKStudioContext`:
496
553
 
497
554
  ```tsx
498
- // This uses the explicit config, not the Studio workspace
499
- <SanityApp config={{projectId: 'other-project', dataset: 'staging'}} fallback={<Loading />}>
555
+ <SanityApp
556
+ resources={{default: {projectId: 'other-project', dataset: 'staging'}}}
557
+ fallback={<Loading />}
558
+ >
500
559
  <MyComponent />
501
560
  </SanityApp>
502
561
  ```
@@ -507,32 +566,35 @@ If the Studio provides a reactive token source via `workspace.auth.token`, the S
507
566
 
508
567
  For older Studios that don't expose a token source, the SDK falls back to discovering the auth token from `localStorage` or cookie auth.
509
568
 
510
- #### Migrating from `studioMode`
569
+ #### `studioMode` has been removed
511
570
 
512
- The `studioMode` config field is deprecated. If you are currently using it, the recommended replacement is to use the zero-config `SDKStudioContext` approach described above — which requires no `config` prop at all.
571
+ The `studioMode` config field was removed in v3. The recommended replacement is to use the zero-config `SDKStudioContext` approach described above — which requires no `config` prop at all.
513
572
 
514
- If you need to pass an explicit config, replace `studioMode` with `studio`:
573
+ If you previously used `studioMode`, replace it with `studio`:
515
574
 
516
575
  ```diff
517
576
  const config: SanityConfig = {
518
- projectId: 'my-project',
519
- dataset: 'production',
577
+ - projectId: 'my-project',
578
+ - dataset: 'production',
520
579
  - studioMode: { enabled: true },
521
580
  + studio: {},
522
581
  }
523
582
  ```
524
583
 
525
- ---
584
+ See the [Migration Guide](./guides/0-Migration-Guide.md) for all v3 breaking changes and upgrade steps.
526
585
 
527
- ### TypeScript & TypeGen
586
+ ---
528
587
 
529
- ```bash
530
- # Generate types from your schema
531
- npx sanity typegen generate
532
- ```
588
+ ### TypeScript
533
589
 
534
590
  ```tsx
535
- import type {Article} from './sanity.types'
591
+ import {type SanityDocument} from '@sanity/sdk-react'
592
+
593
+ interface Article extends SanityDocument {
594
+ _type: 'article'
595
+ title: string
596
+ body: string
597
+ }
536
598
 
537
599
  const {data} = useDocument<Article>(handle)
538
600
  // data is typed as Article