Skip to content

Commit 8b13e97

Browse files
refactor(react): update useDocument to return data (#510)
* refactor: update useDocument hook to return data under a `data` for consistency across the SDK * fix: ensure useDocument hook returns an empty string as default for name * fix: correct path parameter in useEditDocument hook for clarity * Update 0-Migration-Guide.md Co-authored-by: Cole Peters <[email protected]> --------- Co-authored-by: Cole Peters <[email protected]>
1 parent c7be7ac commit 8b13e97

File tree

7 files changed

+88
-37
lines changed

7 files changed

+88
-37
lines changed

apps/kitchensink-react/src/DocumentCollection/DocumentEditorRoute.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ function Editor() {
3737
const canUnpublish = useDocumentPermissions(unpublishDocument(docHandle))
3838
const canDiscard = useDocumentPermissions(discardDocument(docHandle))
3939

40-
const name = useDocument({...docHandle, path: 'name'}) ?? ''
40+
const {data: name = ''} = useDocument({...docHandle, path: 'name'})
4141
const setName = useEditDocument({...docHandle, path: 'name'})
4242

4343
const [value, setValue] = useState('')
4444

45-
const document = useDocument({...docHandle})
45+
const {data: document} = useDocument({...docHandle})
4646
const setDocument = useEditDocument(docHandle)
4747

4848
return (

apps/kitchensink-react/src/DocumentCollection/MultiResourceRoute.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ const doc2 = createDocumentHandle({
1717
})
1818

1919
export function MultiResourceRoute(): JSX.Element {
20-
const author = useDocument(doc)
21-
const dog = useDocument(doc2)
20+
const {data: author} = useDocument(doc)
21+
const {data: dog} = useDocument(doc2)
2222
const setAuthorName = useEditDocument({...doc, path: 'name'})
2323
const setDogName = useEditDocument({...doc2, path: 'name'})
2424

apps/kitchensink-react/src/DocumentCollection/OrgDocumentExplorerRoute.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function DocumentEditorDialog({
5151
open,
5252
}: DocumentEditorDialogProps) {
5353
const handle = {documentId, documentType}
54-
const document = useDocument(handle)
54+
const {data: document} = useDocument(handle)
5555
const editDocument = useEditDocument(handle)
5656
const isSaving = useDocumentSyncStatus(handle)
5757

packages/react/guides/0-Migration-Guide.md

+36
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,42 @@ Also renamed associated types to match:
166166
- `UseProjectionOptions``useDocumentProjectionOptions`
167167
- `UseProjectionResults``useDocumentProjectionResults`
168168

169+
3. Updated `useDocument` return structure
170+
171+
The `useDocument` hook now returns its data under a `data` property for consistency with other hooks in the SDK.
172+
173+
**Before:**
174+
175+
```typescript
176+
// Full document
177+
const product = useDocument({documentId: '123', documentType: 'product'})
178+
console.log(product?.title)
179+
180+
// Path selection
181+
const title = useDocument({
182+
documentId: '123',
183+
documentType: 'product',
184+
path: 'title',
185+
})
186+
console.log(title)
187+
```
188+
189+
**After:**
190+
191+
```typescript
192+
// Full document - now returns {data: T | null}
193+
const {data: product} = useDocument({documentId: '123', documentType: 'product'})
194+
console.log(product?.title) // product is possibly null
195+
196+
// Path selection - now returns {data: T | undefined}
197+
const {data: title} = useDocument({
198+
documentId: '123',
199+
documentType: 'product',
200+
path: 'title',
201+
})
202+
console.log(title) // title is possibly undefined
203+
```
204+
169205
## Migrating to @sanity/sdk-react@0.0.0-rc.7
170206

171207
This version introduces significant improvements for TypeScript users by integrating [Sanity TypeGen](https://www.sanity.io/docs/sanity-typegen). While Typegen is optional, using it unlocks strong type safety for documents, queries, and projections. These changes also refine hook signatures for better consistency, even for JavaScript users.

packages/react/src/hooks/document/useDocument.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe('useDocument hook', () => {
8585

8686
const {result} = renderHook(() => useDocument({documentId: 'doc1', documentType: 'book'}))
8787

88-
expect(result.current).toEqual(book)
88+
expect(result.current.data).toEqual(book)
8989
expect(getCurrent).toHaveBeenCalled()
9090
expect(subscribe).toHaveBeenCalled()
9191
})

packages/react/src/hooks/document/useDocument.ts

+41-26
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,35 @@ import type {useDocumentProjection} from '../projection/useDocumentProjection'
99
// eslint-disable-next-line import/consistent-type-specifier-style, unused-imports/no-unused-imports
1010
import type {useQuery} from '../query/useQuery'
1111

12+
const useDocumentValue = createStateSourceHook({
13+
// Pass options directly to getDocumentState
14+
getState: (instance, options: DocumentOptions<string | undefined>) =>
15+
getDocumentState(instance, options),
16+
// Pass options directly to getDocumentState for checking current value
17+
shouldSuspend: (instance, {path: _path, ...options}: DocumentOptions<string | undefined>) =>
18+
getDocumentState(instance, options).getCurrent() === undefined,
19+
// Extract handle part for resolveDocument
20+
suspender: (instance, options: DocumentOptions<string | undefined>) =>
21+
resolveDocument(instance, options),
22+
getConfig: identity as (
23+
options: DocumentOptions<string | undefined>,
24+
) => DocumentOptions<string | undefined>,
25+
})
26+
27+
const wrapHookWithData = <TParams extends unknown[], TReturn>(
28+
useValue: (...params: TParams) => TReturn,
29+
) => {
30+
function useHook(...params: TParams) {
31+
return {data: useValue(...params)}
32+
}
33+
return useHook
34+
}
35+
1236
interface UseDocument {
1337
/** @internal */
1438
<TDocumentType extends string, TDataset extends string, TProjectId extends string = string>(
1539
options: DocumentOptions<undefined, TDocumentType, TDataset, TProjectId>,
16-
): SanityDocumentResult<TDocumentType, TDataset, TProjectId> | null
40+
): {data: SanityDocumentResult<TDocumentType, TDataset, TProjectId> | null}
1741

1842
/** @internal */
1943
<
@@ -23,12 +47,12 @@ interface UseDocument {
2347
TProjectId extends string = string,
2448
>(
2549
options: DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>,
26-
): JsonMatch<SanityDocumentResult<TDocumentType, TDataset, TProjectId>, TPath> | undefined
50+
): {data: JsonMatch<SanityDocumentResult<TDocumentType, TDataset, TProjectId>, TPath> | undefined}
2751

2852
/** @internal */
29-
<TData>(options: DocumentOptions<undefined>): TData | null
53+
<TData>(options: DocumentOptions<undefined>): {data: TData | null}
3054
/** @internal */
31-
<TData>(options: DocumentOptions<string>): TData | undefined
55+
<TData>(options: DocumentOptions<string>): {data: TData | undefined}
3256

3357
/**
3458
* ## useDocument via Type Inference (Recommended)
@@ -68,7 +92,7 @@ interface UseDocument {
6892
* }
6993
*
7094
* function ProductView({doc}: ProductViewProps) {
71-
* const product = useDocument({...doc}) // Fully typed product
95+
* const {data: product} = useDocument({...doc}) // Fully typed product
7296
* return <h1>{product.title ?? 'Untitled'}</h1>
7397
* }
7498
* ```
@@ -82,7 +106,7 @@ interface UseDocument {
82106
* }
83107
*
84108
* function ProductTitle({doc}: ProductTitleProps) {
85-
* const title = useDocument({
109+
* const {data: title} = useDocument({
86110
* ...doc,
87111
* path: 'title' // Returns just the title field
88112
* })
@@ -100,8 +124,12 @@ interface UseDocument {
100124
>(
101125
options: DocumentOptions<TPath, TDocumentType, TDataset, TProjectId>,
102126
): TPath extends string
103-
? JsonMatch<SanityDocumentResult<TDocumentType, TDataset, TProjectId>, TPath> | undefined
104-
: SanityDocumentResult<TDocumentType, TDataset, TProjectId> | null
127+
? {
128+
data:
129+
| JsonMatch<SanityDocumentResult<TDocumentType, TDataset, TProjectId>, TPath>
130+
| undefined
131+
}
132+
: {data: SanityDocumentResult<TDocumentType, TDataset, TProjectId> | null}
105133

106134
/**
107135
* @beta
@@ -142,7 +170,7 @@ interface UseDocument {
142170
* }
143171
*
144172
* function BookView({doc}: BookViewProps) {
145-
* const book = useDocument<Book>({...doc})
173+
* const {data: book} = useDocument<Book>({...doc})
146174
* return <h1>{book?.title ?? 'Untitled'} by {book?.author ?? 'Unknown'}</h1>
147175
* }
148176
* ```
@@ -156,7 +184,7 @@ interface UseDocument {
156184
* }
157185
*
158186
* function BookTitle({doc}: BookTitleProps) {
159-
* const title = useDocument<string>({...doc, path: 'title'})
187+
* const {data: title} = useDocument<string>({...doc, path: 'title'})
160188
* return <h1>{title ?? 'Untitled'}</h1>
161189
* }
162190
* ```
@@ -165,12 +193,12 @@ interface UseDocument {
165193
*/
166194
<TData, TPath extends string>(
167195
options: DocumentOptions<TPath>,
168-
): TPath extends string ? TData | undefined : TData | null
196+
): TPath extends string ? {data: TData | undefined} : {data: TData | null}
169197

170198
/**
171199
* @internal
172200
*/
173-
(options: DocumentOptions): unknown
201+
(options: DocumentOptions): {data: unknown}
174202
}
175203

176204
/**
@@ -196,17 +224,4 @@ interface UseDocument {
196224
*
197225
* @function
198226
*/
199-
export const useDocument = createStateSourceHook({
200-
// Pass options directly to getDocumentState
201-
getState: (instance, options: DocumentOptions<string | undefined>) =>
202-
getDocumentState(instance, options),
203-
// Pass options directly to getDocumentState for checking current value
204-
shouldSuspend: (instance, {path: _path, ...options}: DocumentOptions<string | undefined>) =>
205-
getDocumentState(instance, options).getCurrent() === undefined,
206-
// Extract handle part for resolveDocument
207-
suspender: (instance, options: DocumentOptions<string | undefined>) =>
208-
resolveDocument(instance, options),
209-
getConfig: identity as (
210-
options: DocumentOptions<string | undefined>,
211-
) => DocumentOptions<string | undefined>,
212-
}) as UseDocument
227+
export const useDocument = wrapHookWithData(useDocumentValue) as UseDocument

packages/react/src/hooks/document/useEditDocument.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export function useEditDocument<TData>(
115115
*
116116
* function ProductEditor({ productHandle }: ProductEditorProps) {
117117
* // Fetch the document to display its current state (optional)
118-
* const product = useDocument(productHandle);
118+
* const {data: product} = useDocument(productHandle);
119119
* // Get the edit function for the full document
120120
* const editProduct = useEditDocument(productHandle);
121121
*
@@ -162,7 +162,7 @@ export function useEditDocument<TData>(
162162
* };
163163
*
164164
* // Fetch the current price to display it
165-
* const currentPrice = useDocument(priceOptions);
165+
* const {data: currentPrice} = useDocument(priceOptions);
166166
* // Get the edit function for the specific path 'price'
167167
* const editPrice = useEditDocument(priceOptions);
168168
*
@@ -195,7 +195,7 @@ export function useEditDocument<TData>(
195195
* }
196196
*
197197
* function BookEditor({ bookHandle }: BookEditorProps) {
198-
* const book = useDocument<Book>(bookHandle);
198+
* const {data: book} = useDocument<Book>(bookHandle);
199199
* // Provide the explicit type <Book>
200200
* const editBook = useEditDocument<Book>(bookHandle);
201201
*
@@ -235,9 +235,9 @@ export function useEditDocument<TData>(
235235
*
236236
* function AuthorNameEditor({ bookHandle }: AuthorNameEditorProps) {*
237237
* // Fetch current value
238-
* const currentName = useDocument<string>({...bookHandle, path: 'author.name'});
238+
* const {data: currentName} = useDocument<string>({...bookHandle, path: 'author.name'});
239239
* // Provide the explicit type <string> for the path's value
240-
* const editAuthorName = useEditDocument<string>({...bookHandle, 'author.name'});
240+
* const editAuthorName = useEditDocument<string>({...bookHandle, path: 'author.name'});
241241
*
242242
* const handleUpdate = useCallback(() => {
243243
* // Update with a hardcoded string directly

0 commit comments

Comments
 (0)