@sanity/sdk-react 0.0.0-rc.5 → 0.0.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -57
- package/dist/index.d.ts +998 -437
- package/dist/index.js +325 -259
- package/dist/index.js.map +1 -1
- package/package.json +13 -12
- package/src/_exports/sdk-react.ts +4 -1
- package/src/components/SDKProvider.tsx +6 -1
- package/src/components/SanityApp.test.tsx +29 -47
- package/src/components/SanityApp.tsx +12 -11
- package/src/components/auth/AuthBoundary.test.tsx +177 -7
- package/src/components/auth/AuthBoundary.tsx +31 -1
- package/src/components/auth/ConfigurationError.ts +22 -0
- package/src/components/auth/LoginError.tsx +9 -3
- package/src/hooks/auth/useVerifyOrgProjects.test.tsx +136 -0
- package/src/hooks/auth/useVerifyOrgProjects.tsx +48 -0
- package/src/hooks/client/useClient.ts +3 -3
- package/src/hooks/comlink/useManageFavorite.test.ts +276 -27
- package/src/hooks/comlink/useManageFavorite.ts +102 -51
- package/src/hooks/comlink/useWindowConnection.ts +3 -2
- package/src/hooks/datasets/useDatasets.test.ts +80 -0
- package/src/hooks/datasets/useDatasets.ts +2 -1
- package/src/hooks/document/useApplyDocumentActions.ts +105 -31
- package/src/hooks/document/useDocument.test.ts +41 -4
- package/src/hooks/document/useDocument.ts +198 -114
- package/src/hooks/document/useDocumentEvent.test.ts +5 -5
- package/src/hooks/document/useDocumentEvent.ts +67 -23
- package/src/hooks/document/useDocumentPermissions.ts +47 -8
- package/src/hooks/document/useDocumentSyncStatus.test.ts +12 -5
- package/src/hooks/document/useDocumentSyncStatus.ts +41 -14
- package/src/hooks/document/useEditDocument.test.ts +24 -6
- package/src/hooks/document/useEditDocument.ts +238 -133
- package/src/hooks/documents/useDocuments.test.tsx +1 -1
- package/src/hooks/documents/useDocuments.ts +153 -44
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +1 -1
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +120 -47
- package/src/hooks/projection/useProjection.ts +134 -46
- package/src/hooks/projects/useProject.test.ts +80 -0
- package/src/hooks/projects/useProjects.test.ts +77 -0
- package/src/hooks/query/useQuery.test.tsx +4 -4
- package/src/hooks/query/useQuery.ts +115 -43
- package/src/hooks/releases/useActiveReleases.test.tsx +84 -0
- package/src/hooks/releases/useActiveReleases.ts +39 -0
- package/src/hooks/releases/usePerspective.test.tsx +120 -0
- package/src/hooks/releases/usePerspective.ts +49 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// tests/useEditDocument.test.ts
|
|
2
2
|
import {
|
|
3
|
+
createDocumentHandle,
|
|
3
4
|
createSanityInstance,
|
|
4
|
-
type DocumentHandle,
|
|
5
5
|
editDocument,
|
|
6
6
|
getDocumentState,
|
|
7
7
|
resolveDocument,
|
|
@@ -43,11 +43,29 @@ const doc = {
|
|
|
43
43
|
_rev: 'tx0',
|
|
44
44
|
_createdAt: '2025-02-06T00:11:00.000Z',
|
|
45
45
|
_updatedAt: '2025-02-06T00:11:00.000Z',
|
|
46
|
-
} satisfies
|
|
46
|
+
} satisfies Book
|
|
47
47
|
|
|
48
|
-
const docHandle
|
|
48
|
+
const docHandle = createDocumentHandle({
|
|
49
49
|
documentId: 'doc1',
|
|
50
50
|
documentType: 'book',
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Define a single generic TestDocument type
|
|
54
|
+
interface Book extends SanityDocument {
|
|
55
|
+
_type: 'book'
|
|
56
|
+
foo?: string
|
|
57
|
+
extra?: string
|
|
58
|
+
title?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Scope the TestDocument type to the project/datasets used in tests
|
|
62
|
+
type AllTestSchemaTypes = Book
|
|
63
|
+
|
|
64
|
+
// Augment the 'groq' module
|
|
65
|
+
declare module 'groq' {
|
|
66
|
+
interface SanitySchemas {
|
|
67
|
+
'default:default': AllTestSchemaTypes
|
|
68
|
+
}
|
|
51
69
|
}
|
|
52
70
|
|
|
53
71
|
describe('useEditDocument hook', () => {
|
|
@@ -67,7 +85,7 @@ describe('useEditDocument hook', () => {
|
|
|
67
85
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx1'})
|
|
68
86
|
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
69
87
|
|
|
70
|
-
const {result} = renderHook(() => useEditDocument(docHandle, 'foo'))
|
|
88
|
+
const {result} = renderHook(() => useEditDocument<string>({...docHandle, path: 'foo'}))
|
|
71
89
|
const promise = result.current('newValue')
|
|
72
90
|
expect(editDocument).toHaveBeenCalledWith(docHandle, {set: {foo: 'newValue'}})
|
|
73
91
|
expect(apply).toHaveBeenCalledWith(editDocument(docHandle, {set: {foo: 'newValue'}}))
|
|
@@ -106,7 +124,7 @@ describe('useEditDocument hook', () => {
|
|
|
106
124
|
const apply = vi.fn().mockResolvedValue({transactionId: 'tx3'})
|
|
107
125
|
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
108
126
|
|
|
109
|
-
const {result} = renderHook(() => useEditDocument(docHandle, 'foo'))
|
|
127
|
+
const {result} = renderHook(() => useEditDocument<string>({...docHandle, path: 'foo'}))
|
|
110
128
|
const promise = result.current((prev: unknown) => `${prev}Updated`) // 'bar' becomes 'barUpdated'
|
|
111
129
|
expect(editDocument).toHaveBeenCalledWith(docHandle, {set: {foo: 'barUpdated'}})
|
|
112
130
|
expect(apply).toHaveBeenCalledWith(editDocument(docHandle, {set: {foo: 'barUpdated'}}))
|
|
@@ -145,7 +163,7 @@ describe('useEditDocument hook', () => {
|
|
|
145
163
|
vi.mocked(useApplyDocumentActions).mockReturnValue(fakeApply)
|
|
146
164
|
|
|
147
165
|
const {result} = renderHook(() => useEditDocument(docHandle))
|
|
148
|
-
expect(() => result.current('notAnObject' as unknown as
|
|
166
|
+
expect(() => result.current('notAnObject' as unknown as Book)).toThrowError(
|
|
149
167
|
'No path was provided to `useEditDocument` and the value provided was not a document object.',
|
|
150
168
|
)
|
|
151
169
|
})
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ActionsResult,
|
|
3
|
-
type
|
|
3
|
+
type DocumentOptions,
|
|
4
4
|
editDocument,
|
|
5
5
|
getDocumentState,
|
|
6
6
|
type JsonMatch,
|
|
7
|
-
type JsonMatchPath,
|
|
8
7
|
resolveDocument,
|
|
9
8
|
} from '@sanity/sdk'
|
|
10
|
-
import {type
|
|
9
|
+
import {type SanityDocumentResult} from 'groq'
|
|
11
10
|
import {useCallback} from 'react'
|
|
12
11
|
|
|
13
12
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
@@ -15,178 +14,281 @@ import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
|
15
14
|
|
|
16
15
|
const ignoredKeys = ['_id', '_type', '_createdAt', '_updatedAt', '_rev']
|
|
17
16
|
|
|
18
|
-
type Updater<TValue> = TValue | ((
|
|
17
|
+
type Updater<TValue> = TValue | ((currentValue: TValue) => TValue)
|
|
19
18
|
|
|
19
|
+
// Overload 1: No path, relies on Typegen
|
|
20
20
|
/**
|
|
21
|
+
* @beta
|
|
22
|
+
* Edit an entire document, relying on Typegen for the type.
|
|
21
23
|
*
|
|
24
|
+
* @param options - Document options including `documentId`, `documentType`, and optionally `projectId`/`dataset`.
|
|
25
|
+
* @returns A stable function to update the document state. Accepts either the new document state or an updater function `(currentValue) => nextValue`.
|
|
26
|
+
* Returns a promise resolving to the {@link ActionsResult}.
|
|
27
|
+
*/
|
|
28
|
+
export function useEditDocument<
|
|
29
|
+
TDocumentType extends string = string,
|
|
30
|
+
TDataset extends string = string,
|
|
31
|
+
TProjectId extends string = string,
|
|
32
|
+
>(
|
|
33
|
+
options: DocumentOptions<undefined, TDocumentType, TDataset, TProjectId>,
|
|
34
|
+
): (
|
|
35
|
+
nextValue: Updater<SanityDocumentResult<TDocumentType, TDataset, TProjectId>>,
|
|
36
|
+
) => Promise<ActionsResult<SanityDocumentResult<TDocumentType, TDataset, TProjectId>>>
|
|
37
|
+
|
|
38
|
+
// Overload 2: Path provided, relies on Typegen
|
|
39
|
+
/**
|
|
22
40
|
* @beta
|
|
41
|
+
* Edit a specific path within a document, relying on Typegen for the type.
|
|
23
42
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
43
|
+
* @param options - Document options including `documentId`, `documentType`, `path`, and optionally `projectId`/`dataset`.
|
|
44
|
+
* @returns A stable function to update the value at the specified path. Accepts either the new value or an updater function `(currentValue) => nextValue`.
|
|
45
|
+
* Returns a promise resolving to the {@link ActionsResult}.
|
|
46
|
+
*/
|
|
47
|
+
export function useEditDocument<
|
|
48
|
+
TPath extends string = string,
|
|
49
|
+
TDocumentType extends string = string,
|
|
50
|
+
TDataset extends string = string,
|
|
51
|
+
TProjectId extends string = string,
|
|
52
|
+
>(
|
|
53
|
+
options: DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>,
|
|
54
|
+
): (
|
|
55
|
+
nextValue: Updater<JsonMatch<SanityDocumentResult<TDocumentType, TDataset, TProjectId>, TPath>>,
|
|
56
|
+
) => Promise<ActionsResult<SanityDocumentResult<TDocumentType, TDataset, TProjectId>>>
|
|
57
|
+
|
|
58
|
+
// Overload 3: Explicit type, no path
|
|
59
|
+
/**
|
|
60
|
+
* @beta
|
|
61
|
+
* Edit an entire document with an explicit type `TData`.
|
|
62
|
+
*
|
|
63
|
+
* @param options - Document options including `documentId` and optionally `projectId`/`dataset`.
|
|
64
|
+
* @returns A stable function to update the document state. Accepts either the new document state (`TData`) or an updater function `(currentValue: TData) => nextValue: TData`.
|
|
65
|
+
* Returns a promise resolving to the {@link ActionsResult}.
|
|
66
|
+
*/
|
|
67
|
+
export function useEditDocument<TData>(
|
|
68
|
+
options: DocumentOptions<undefined>,
|
|
69
|
+
): (nextValue: Updater<TData>) => Promise<ActionsResult>
|
|
70
|
+
|
|
71
|
+
// Overload 4: Explicit type, path provided
|
|
72
|
+
/**
|
|
73
|
+
* @beta
|
|
74
|
+
* Edit a specific path within a document with an explicit type `TData`.
|
|
75
|
+
*
|
|
76
|
+
* @param options - Document options including `documentId`, `path`, and optionally `projectId`/`dataset`.
|
|
77
|
+
* @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`.
|
|
78
|
+
* Returns a promise resolving to the {@link ActionsResult}.
|
|
79
|
+
*/
|
|
80
|
+
export function useEditDocument<TData>(
|
|
81
|
+
options: DocumentOptions<string>,
|
|
82
|
+
): (nextValue: Updater<TData>) => Promise<ActionsResult>
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @beta
|
|
86
|
+
* Provides a stable function to apply edits to a document or a specific path within it.
|
|
26
87
|
*
|
|
27
88
|
* @category Documents
|
|
28
|
-
* @
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* This hook simplifies editing documents by automatically:
|
|
91
|
+
* - Comparing the current and next states to determine the minimal set of `set` and `unset` operations required for the update via `editDocument`.
|
|
92
|
+
* - Handling both full document updates and specific path updates.
|
|
93
|
+
* - Supporting functional updates (e.g., `edit(prev => ({...prev, title: 'New'}))`).
|
|
94
|
+
* - Integrating with the active {@link SanityInstance} context.
|
|
95
|
+
* - Utilizing `useApplyDocumentActions` internally for optimistic updates and transaction handling.
|
|
96
|
+
*
|
|
97
|
+
* It offers several overloads for flexibility:
|
|
98
|
+
* 1. **Typegen (Full Document):** Edit the entire document, inferring types from your schema.
|
|
99
|
+
* 2. **Typegen (Specific Path):** Edit a specific field, inferring types.
|
|
100
|
+
* 3. **Explicit Type (Full Document):** Edit the entire document with a manually specified type.
|
|
101
|
+
* 4. **Explicit Type (Specific Path):** Edit a specific field with a manually specified type.
|
|
102
|
+
*
|
|
103
|
+
* This hook relies on the document state being loaded. If the document is not yet available
|
|
104
|
+
* (e.g., during initial load), the component using this hook will suspend.
|
|
105
|
+
*
|
|
106
|
+
* @example Basic Usage (Typegen, Full Document)
|
|
32
107
|
* ```tsx
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
108
|
+
* import {useCallback} from 'react';
|
|
109
|
+
* import {useEditDocument, useDocument, type DocumentHandle} from '@sanity/sdk-react'
|
|
110
|
+
*
|
|
111
|
+
* // Assume 'product' schema has a 'title' field (string)
|
|
112
|
+
* interface ProductEditorProps {
|
|
113
|
+
* productHandle: DocumentHandle<'product'> // Typegen infers 'product' type
|
|
38
114
|
* }
|
|
39
115
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
116
|
+
* function ProductEditor({ productHandle }: ProductEditorProps) {
|
|
117
|
+
* // Fetch the document to display its current state (optional)
|
|
118
|
+
* const product = useDocument(productHandle);
|
|
119
|
+
* // Get the edit function for the full document
|
|
120
|
+
* const editProduct = useEditDocument(productHandle);
|
|
42
121
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
122
|
+
* // Use useCallback for stable event handlers
|
|
123
|
+
* const handleTitleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
124
|
+
* const newTitle = event.target.value;
|
|
125
|
+
* // Use the functional updater for safe partial updates
|
|
126
|
+
* editProduct(prev => ({
|
|
127
|
+
* ...prev,
|
|
128
|
+
* title: newTitle,
|
|
129
|
+
* })).
|
|
130
|
+
* }, [editProduct]);
|
|
46
131
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
132
|
+
* return (
|
|
133
|
+
* <div>
|
|
134
|
+
* <label>
|
|
135
|
+
* Product Title:
|
|
136
|
+
* <input
|
|
137
|
+
* type="text"
|
|
138
|
+
* value={product?.title ?? ''}
|
|
139
|
+
* onChange={handleTitleChange}
|
|
140
|
+
* />
|
|
141
|
+
* </label>
|
|
142
|
+
* </div>
|
|
143
|
+
* );
|
|
144
|
+
* }
|
|
50
145
|
* ```
|
|
51
146
|
*
|
|
52
|
-
* @example
|
|
147
|
+
* @example Editing a Specific Path (Typegen)
|
|
53
148
|
* ```tsx
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
149
|
+
* import React, { useCallback } from 'react';
|
|
150
|
+
* import {useEditDocument, useDocument, type DocumentHandle, type DocumentOptions} from '@sanity/sdk-react'
|
|
151
|
+
*
|
|
152
|
+
* // Assume 'product' schema has a 'price' field (number)
|
|
153
|
+
* interface ProductPriceEditorProps {
|
|
154
|
+
* productHandle: DocumentHandle<'product'>;
|
|
59
155
|
* }
|
|
60
156
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
157
|
+
* function ProductPriceEditor({ productHandle }: ProductPriceEditorProps) {
|
|
158
|
+
* // Construct DocumentOptions internally, combining the handle and a hardcoded path
|
|
159
|
+
* const priceOptions {
|
|
160
|
+
* ...productHandle,
|
|
161
|
+
* path: 'price', // Hardcode the path to edit
|
|
162
|
+
* };
|
|
63
163
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
164
|
+
* // Fetch the current price to display it
|
|
165
|
+
* const currentPrice = useDocument(priceOptions);
|
|
166
|
+
* // Get the edit function for the specific path 'price'
|
|
167
|
+
* const editPrice = useEditDocument(priceOptions);
|
|
168
|
+
*
|
|
169
|
+
* const handleSetFixedPrice = useCallback(() => {
|
|
170
|
+
* // Update the price directly to a hardcoded value
|
|
171
|
+
* editPrice(99.99)
|
|
172
|
+
* }, [editPrice]);
|
|
173
|
+
*
|
|
174
|
+
* return (
|
|
175
|
+
* <div>
|
|
176
|
+
* <p>Current Price: {currentPrice}</p>
|
|
177
|
+
* <button onClick={handleSetFixedPrice}>
|
|
178
|
+
* Set Price to $99.99
|
|
179
|
+
* </button>
|
|
180
|
+
* </div>
|
|
181
|
+
* );
|
|
66
182
|
* }
|
|
67
183
|
*
|
|
68
|
-
* return (
|
|
69
|
-
* <>
|
|
70
|
-
* <button onClick={incrementCount}>
|
|
71
|
-
* Increment
|
|
72
|
-
* </button>
|
|
73
|
-
* Current count: {count}
|
|
74
|
-
* </>
|
|
75
|
-
* )
|
|
76
184
|
* ```
|
|
77
|
-
*/
|
|
78
|
-
export function useEditDocument<
|
|
79
|
-
TDocument extends SanityDocument,
|
|
80
|
-
TPath extends JsonMatchPath<TDocument>,
|
|
81
|
-
>(
|
|
82
|
-
docHandle: DocumentHandle<TDocument>,
|
|
83
|
-
path: TPath,
|
|
84
|
-
): (nextValue: Updater<JsonMatch<TDocument, TPath>>) => Promise<ActionsResult<TDocument>>
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
185
|
*
|
|
88
|
-
* @
|
|
89
|
-
*
|
|
90
|
-
* ## useEditDocument(doc)
|
|
91
|
-
* Edit an entire document
|
|
92
|
-
* @param docHandle - The document to be edited, specified as a DocumentHandle.
|
|
93
|
-
* The hook will automatically use the Sanity instance that matches the project and dataset specified in the handle.
|
|
94
|
-
* @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.
|
|
95
|
-
* @example
|
|
186
|
+
* @example Usage with Explicit Types (Full Document)
|
|
96
187
|
* ```tsx
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* documentType: 'product',
|
|
100
|
-
* projectId: 'abc123',
|
|
101
|
-
* dataset: 'production'
|
|
102
|
-
* }
|
|
103
|
-
*
|
|
104
|
-
* const myDocument = useDocument(myDocumentHandle)
|
|
105
|
-
* const { title, price } = myDocument ?? {}
|
|
188
|
+
* import React, { useCallback } from 'react';
|
|
189
|
+
* import {useEditDocument, useDocument, type DocumentHandle, type SanityDocument} from '@sanity/sdk-react'
|
|
106
190
|
*
|
|
107
|
-
*
|
|
191
|
+
* interface Book extends SanityDocument { _type: 'book', title: string, author: string }
|
|
108
192
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* // Use an updater function to update the document state based on the previous state
|
|
112
|
-
* editMyDocument(previousDocument => ({
|
|
113
|
-
* ...previousDocument,
|
|
114
|
-
* [name]: value
|
|
115
|
-
* }))
|
|
193
|
+
* interface BookEditorProps {
|
|
194
|
+
* bookHandle: DocumentHandle // No documentType needed if providing TData
|
|
116
195
|
* }
|
|
117
196
|
*
|
|
118
|
-
* function
|
|
119
|
-
* const
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
197
|
+
* function BookEditor({ bookHandle }: BookEditorProps) {
|
|
198
|
+
* const book = useDocument<Book>(bookHandle);
|
|
199
|
+
* // Provide the explicit type <Book>
|
|
200
|
+
* const editBook = useEditDocument<Book>(bookHandle);
|
|
201
|
+
*
|
|
202
|
+
* const handleAuthorChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
|
203
|
+
* const newAuthor = event.target.value;
|
|
204
|
+
* editBook(prev => ({
|
|
205
|
+
* ...prev,
|
|
206
|
+
* author: newAuthor
|
|
126
207
|
* }))
|
|
127
|
-
* }
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
208
|
+
* }, [editBook]);
|
|
209
|
+
*
|
|
210
|
+
* return (
|
|
211
|
+
* <div>
|
|
212
|
+
* <label>
|
|
213
|
+
* Book Author:
|
|
214
|
+
* <input
|
|
215
|
+
* type="text"
|
|
216
|
+
* value={book?.author ?? ''}
|
|
217
|
+
* onChange={handleAuthorChange}
|
|
218
|
+
* />
|
|
219
|
+
* </label>
|
|
220
|
+
* </div>
|
|
221
|
+
* );
|
|
133
222
|
* }
|
|
134
223
|
*
|
|
135
|
-
* return (
|
|
136
|
-
* <>
|
|
137
|
-
* <form onSubmit={e => e.preventDefault()}>
|
|
138
|
-
* <input name='title' type='text' value={title} onChange={handleFieldChange} />
|
|
139
|
-
* <input name='price' type='number' value={price} onChange={handleFieldChange} />
|
|
140
|
-
* <input
|
|
141
|
-
* name='salePrice'
|
|
142
|
-
* type='checkbox'
|
|
143
|
-
* checked={myDocument && 'salePrice' in myDocument}
|
|
144
|
-
* onChange={handleSaleChange}
|
|
145
|
-
* />
|
|
146
|
-
* </form>
|
|
147
|
-
* <pre><code>
|
|
148
|
-
* {JSON.stringify(myDocument, null, 2)}
|
|
149
|
-
* </code></pre>
|
|
150
|
-
* </>
|
|
151
|
-
* )
|
|
152
224
|
* ```
|
|
153
|
-
*/
|
|
154
|
-
export function useEditDocument<TDocument extends SanityDocument>(
|
|
155
|
-
docHandle: DocumentHandle<TDocument>,
|
|
156
|
-
): (nextValue: Updater<TDocument>) => Promise<ActionsResult<TDocument>>
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
225
|
*
|
|
160
|
-
* @
|
|
226
|
+
* @example Usage with Explicit Types (Specific Path)
|
|
227
|
+
* ```tsx
|
|
228
|
+
* import React, { useCallback } from 'react';
|
|
229
|
+
* import {useEditDocument, useDocument, type DocumentHandle, type DocumentOptions} from '@sanity/sdk-react'
|
|
230
|
+
*
|
|
231
|
+
* // Assume 'book' has 'author.name' (string)
|
|
232
|
+
* interface AuthorNameEditorProps {
|
|
233
|
+
* bookHandle: DocumentHandle; // No documentType needed if providing TData for path
|
|
234
|
+
* }
|
|
235
|
+
*
|
|
236
|
+
* function AuthorNameEditor({ bookHandle }: AuthorNameEditorProps) {*
|
|
237
|
+
* // Fetch current value
|
|
238
|
+
* const currentName = useDocument<string>({...bookHandle, path: 'author.name'});
|
|
239
|
+
* // Provide the explicit type <string> for the path's value
|
|
240
|
+
* const editAuthorName = useEditDocument<string>({...bookHandle, 'author.name'});
|
|
161
241
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
242
|
+
* const handleUpdate = useCallback(() => {
|
|
243
|
+
* // Update with a hardcoded string directly
|
|
244
|
+
* editAuthorName('Jane Doe')
|
|
245
|
+
* }, [editAuthorName]);
|
|
246
|
+
*
|
|
247
|
+
* return (
|
|
248
|
+
* <div>
|
|
249
|
+
* <p>Current Author Name: {currentName}</p>
|
|
250
|
+
* <button onClick={handleUpdate} disabled={currentName === undefined}>
|
|
251
|
+
* Set Author Name to Jane Doe
|
|
252
|
+
* </button>
|
|
253
|
+
* </div>
|
|
254
|
+
* );
|
|
255
|
+
* }
|
|
256
|
+
*
|
|
257
|
+
* ```
|
|
165
258
|
*/
|
|
166
|
-
export function useEditDocument(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
): (updater: Updater<unknown>) => Promise<ActionsResult> {
|
|
170
|
-
const instance = useSanityInstance(
|
|
259
|
+
export function useEditDocument({
|
|
260
|
+
path,
|
|
261
|
+
...doc
|
|
262
|
+
}: DocumentOptions<string | undefined>): (updater: Updater<unknown>) => Promise<ActionsResult> {
|
|
263
|
+
const instance = useSanityInstance(doc)
|
|
171
264
|
const apply = useApplyDocumentActions()
|
|
172
265
|
const isDocumentReady = useCallback(
|
|
173
|
-
() => getDocumentState(instance,
|
|
174
|
-
[instance,
|
|
266
|
+
() => getDocumentState(instance, doc).getCurrent() !== undefined,
|
|
267
|
+
[instance, doc],
|
|
175
268
|
)
|
|
176
|
-
if (!isDocumentReady()) throw resolveDocument(instance,
|
|
269
|
+
if (!isDocumentReady()) throw resolveDocument(instance, doc)
|
|
177
270
|
|
|
178
271
|
return (updater: Updater<unknown>) => {
|
|
179
|
-
|
|
272
|
+
const currentPath = path
|
|
273
|
+
|
|
274
|
+
if (currentPath) {
|
|
275
|
+
const stateWithOptions = getDocumentState(instance, {...doc, path})
|
|
276
|
+
const currentValue = stateWithOptions.getCurrent()
|
|
277
|
+
|
|
180
278
|
const nextValue =
|
|
181
279
|
typeof updater === 'function'
|
|
182
|
-
? updater(
|
|
280
|
+
? (updater as (prev: typeof currentValue) => typeof currentValue)(currentValue)
|
|
183
281
|
: updater
|
|
184
282
|
|
|
185
|
-
return apply(editDocument(
|
|
283
|
+
return apply(editDocument(doc, {set: {[currentPath]: nextValue}}))
|
|
186
284
|
}
|
|
187
285
|
|
|
188
|
-
const
|
|
189
|
-
const
|
|
286
|
+
const fullDocState = getDocumentState(instance, {...doc, path})
|
|
287
|
+
const current = fullDocState.getCurrent() as object | null | undefined
|
|
288
|
+
const nextValue =
|
|
289
|
+
typeof updater === 'function'
|
|
290
|
+
? (updater as (prev: typeof current) => typeof current)(current)
|
|
291
|
+
: updater
|
|
190
292
|
|
|
191
293
|
if (typeof nextValue !== 'object' || !nextValue) {
|
|
192
294
|
throw new Error(
|
|
@@ -197,11 +299,14 @@ export function useEditDocument(
|
|
|
197
299
|
const allKeys = Object.keys({...current, ...nextValue})
|
|
198
300
|
const editActions = allKeys
|
|
199
301
|
.filter((key) => !ignoredKeys.includes(key))
|
|
200
|
-
.filter(
|
|
302
|
+
.filter(
|
|
303
|
+
(key) =>
|
|
304
|
+
current?.[key as keyof typeof current] !== (nextValue as Record<string, unknown>)[key],
|
|
305
|
+
)
|
|
201
306
|
.map((key) =>
|
|
202
307
|
key in nextValue
|
|
203
|
-
? editDocument(
|
|
204
|
-
: editDocument(
|
|
308
|
+
? editDocument(doc, {set: {[key]: (nextValue as Record<string, unknown>)[key]}})
|
|
309
|
+
: editDocument(doc, {unset: [key]}),
|
|
205
310
|
)
|
|
206
311
|
|
|
207
312
|
return apply(editActions)
|
|
@@ -68,7 +68,7 @@ describe('useDocuments', () => {
|
|
|
68
68
|
},
|
|
69
69
|
]
|
|
70
70
|
|
|
71
|
-
vi.mocked(useQuery).mockImplementation((query, options) => {
|
|
71
|
+
vi.mocked(useQuery).mockImplementation(({query, ...options}) => {
|
|
72
72
|
const result = evaluateSync(parse(query), {dataset, params: options?.params}).get()
|
|
73
73
|
return {
|
|
74
74
|
data: result,
|