@sanity/sdk-react 0.0.0-alpha.11 → 0.0.0-alpha.12
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 +0 -10
- package/dist/_chunks-es/context.js.map +1 -1
- package/dist/_chunks-es/useLogOut.js.map +1 -1
- package/dist/components.js +8 -1
- package/dist/components.js.map +1 -1
- package/dist/context.d.ts +4 -2
- package/dist/hooks.d.ts +401 -92
- package/dist/hooks.js +36 -11
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/_exports/hooks.ts +4 -2
- package/src/_exports/index.ts +1 -1
- package/src/components/SanityApp.tsx +8 -0
- package/src/components/auth/AuthBoundary.tsx +3 -2
- package/src/components/utils.ts +3 -0
- package/src/context/SanityProvider.tsx +4 -2
- package/src/hooks/auth/useAuthState.tsx +0 -2
- package/src/hooks/auth/useCurrentUser.tsx +2 -1
- package/src/hooks/client/useClient.ts +1 -0
- package/src/hooks/comlink/useFrameConnection.test.tsx +45 -10
- package/src/hooks/comlink/useFrameConnection.ts +24 -5
- package/src/hooks/comlink/useWindowConnection.test.ts +43 -12
- package/src/hooks/comlink/useWindowConnection.ts +13 -1
- package/src/hooks/context/useSanityInstance.ts +1 -1
- package/src/hooks/document/useApplyActions.ts +45 -1
- package/src/hooks/document/useDocument.ts +77 -3
- package/src/hooks/document/useDocumentEvent.ts +23 -1
- package/src/hooks/document/useDocumentSyncStatus.test.ts +1 -1
- package/src/hooks/document/useDocumentSyncStatus.ts +25 -2
- package/src/hooks/document/useEditDocument.ts +118 -3
- package/src/hooks/document/usePermissions.ts +28 -0
- package/src/hooks/documentCollection/useDocuments.ts +15 -7
- package/src/hooks/preview/usePreview.tsx +7 -4
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {type Message, type Node} from '@sanity/comlink'
|
|
1
|
+
import {type Message, type Node, type Status} from '@sanity/comlink'
|
|
2
2
|
import {getOrCreateNode, releaseNode} from '@sanity/sdk'
|
|
3
3
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
4
|
|
|
5
|
-
import {renderHook} from '../../../test/test-utils'
|
|
5
|
+
import {act, renderHook} from '../../../test/test-utils'
|
|
6
6
|
import {useWindowConnection} from './useWindowConnection'
|
|
7
7
|
|
|
8
8
|
vi.mock(import('@sanity/sdk'), async (importOriginal) => {
|
|
@@ -26,24 +26,55 @@ interface AnotherMessage {
|
|
|
26
26
|
|
|
27
27
|
type TestMessages = TestMessage | AnotherMessage
|
|
28
28
|
|
|
29
|
-
function createMockNode() {
|
|
30
|
-
return {
|
|
31
|
-
// return unsubscribe function
|
|
32
|
-
on: vi.fn(() => () => {}),
|
|
33
|
-
post: vi.fn(),
|
|
34
|
-
stop: vi.fn(),
|
|
35
|
-
} as unknown as Node<Message, Message>
|
|
36
|
-
}
|
|
37
|
-
|
|
38
29
|
describe('useWindowConnection', () => {
|
|
39
30
|
let node: Node<Message, Message>
|
|
31
|
+
let statusCallback: ((status: Status) => void) | null = null
|
|
32
|
+
|
|
33
|
+
function createMockNode() {
|
|
34
|
+
return {
|
|
35
|
+
// return unsubscribe function
|
|
36
|
+
on: vi.fn(() => () => {}),
|
|
37
|
+
post: vi.fn(),
|
|
38
|
+
stop: vi.fn(),
|
|
39
|
+
onStatus: vi.fn((callback) => {
|
|
40
|
+
statusCallback = callback
|
|
41
|
+
return () => {}
|
|
42
|
+
}),
|
|
43
|
+
} as unknown as Node<Message, Message>
|
|
44
|
+
}
|
|
40
45
|
|
|
41
46
|
beforeEach(() => {
|
|
47
|
+
statusCallback = null
|
|
42
48
|
node = createMockNode()
|
|
43
|
-
|
|
44
49
|
vi.mocked(getOrCreateNode).mockReturnValue(node as unknown as Node<Message, Message>)
|
|
45
50
|
})
|
|
46
51
|
|
|
52
|
+
it('should initialize with idle status', () => {
|
|
53
|
+
const {result} = renderHook(() =>
|
|
54
|
+
useWindowConnection<TestMessages, TestMessages>({
|
|
55
|
+
name: 'test',
|
|
56
|
+
connectTo: 'window',
|
|
57
|
+
}),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
expect(result.current.status).toBe('idle')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should update status to connected when node connects', async () => {
|
|
64
|
+
const {result} = renderHook(() =>
|
|
65
|
+
useWindowConnection<TestMessages, TestMessages>({
|
|
66
|
+
name: 'test',
|
|
67
|
+
connectTo: 'window',
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
expect(result.current.status).toBe('idle')
|
|
72
|
+
act(() => {
|
|
73
|
+
statusCallback?.('connected')
|
|
74
|
+
})
|
|
75
|
+
expect(result.current.status).toBe('connected')
|
|
76
|
+
})
|
|
77
|
+
|
|
47
78
|
it('should register message handlers', () => {
|
|
48
79
|
const mockHandler = vi.fn()
|
|
49
80
|
const mockData = {someData: 'test'}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import {type Status} from '@sanity/comlink'
|
|
1
2
|
import {type FrameMessage, getOrCreateNode, releaseNode, type WindowMessage} from '@sanity/sdk'
|
|
2
|
-
import {useCallback, useEffect, useMemo} from 'react'
|
|
3
|
+
import {useCallback, useEffect, useMemo, useState} from 'react'
|
|
3
4
|
|
|
4
5
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
6
|
|
|
@@ -27,6 +28,7 @@ export interface WindowConnection<TMessage extends WindowMessage> {
|
|
|
27
28
|
type: TType,
|
|
28
29
|
data?: Extract<TMessage, {type: TType}>['data'],
|
|
29
30
|
) => void
|
|
31
|
+
status: Status
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
@@ -38,12 +40,21 @@ export function useWindowConnection<
|
|
|
38
40
|
>(options: UseWindowConnectionOptions<TFrameMessage>): WindowConnection<TWindowMessage> {
|
|
39
41
|
const {name, onMessage, connectTo} = options
|
|
40
42
|
const instance = useSanityInstance()
|
|
43
|
+
const [status, setStatus] = useState<Status>('idle')
|
|
41
44
|
|
|
42
45
|
const node = useMemo(
|
|
43
46
|
() => getOrCreateNode(instance, {name, connectTo}),
|
|
44
47
|
[instance, name, connectTo],
|
|
45
48
|
)
|
|
46
49
|
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const unsubscribe = node.onStatus((newStatus) => {
|
|
52
|
+
setStatus(newStatus)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return unsubscribe
|
|
56
|
+
}, [node, instance, name])
|
|
57
|
+
|
|
47
58
|
useEffect(() => {
|
|
48
59
|
if (!onMessage) return
|
|
49
60
|
|
|
@@ -78,5 +89,6 @@ export function useWindowConnection<
|
|
|
78
89
|
|
|
79
90
|
return {
|
|
80
91
|
sendMessage,
|
|
92
|
+
status,
|
|
81
93
|
}
|
|
82
94
|
}
|
|
@@ -6,7 +6,7 @@ import {SanityInstanceContext} from '../../context/SanityProvider'
|
|
|
6
6
|
/**
|
|
7
7
|
* `useSanityInstance` returns the current Sanity instance from the application context.
|
|
8
8
|
* This must be called from within a `SanityProvider` component.
|
|
9
|
-
* @
|
|
9
|
+
* @internal
|
|
10
10
|
* @returns The current Sanity instance
|
|
11
11
|
* @example
|
|
12
12
|
* ```tsx
|
|
@@ -8,11 +8,55 @@ import {type SanityDocument} from '@sanity/types'
|
|
|
8
8
|
|
|
9
9
|
import {createCallbackHook} from '../helpers/createCallbackHook'
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @beta
|
|
14
|
+
*
|
|
15
|
+
* Provides a callback for applying one or more actions to a document.
|
|
16
|
+
*
|
|
17
|
+
* @category Documents
|
|
18
|
+
* @returns A function that takes one more more {@link DocumentAction}s and returns a promise that resolves to an {@link ActionsResult}.
|
|
19
|
+
* @example Publish or unpublish a document
|
|
20
|
+
* ```
|
|
21
|
+
* import { publishDocument, unpublishDocument } from '@sanity/sdk'
|
|
22
|
+
* import { useApplyActions } from '@sanity/sdk-react'
|
|
23
|
+
*
|
|
24
|
+
* const apply = useApplyActions()
|
|
25
|
+
* const myDocument = { _id: 'my-document-id', _type: 'my-document-type' }
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* <button onClick={() => apply(publishDocument(myDocument))}>Publish</button>
|
|
29
|
+
* <button onClick={() => apply(unpublishDocument(myDocument))}>Unpublish</button>
|
|
30
|
+
* )
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example Create and publish a new document
|
|
34
|
+
* ```
|
|
35
|
+
* import { createDocument, publishDocument } from '@sanity/sdk'
|
|
36
|
+
* import { useApplyActions } from '@sanity/sdk-react'
|
|
37
|
+
*
|
|
38
|
+
* const apply = useApplyActions()
|
|
39
|
+
*
|
|
40
|
+
* const handleCreateAndPublish = () => {
|
|
41
|
+
* const handle = { _id: window.crypto.randomUUID(), _type: 'my-document-type' }
|
|
42
|
+
* apply([
|
|
43
|
+
* createDocument(handle),
|
|
44
|
+
* publishDocument(handle),
|
|
45
|
+
* ])
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* return (
|
|
49
|
+
* <button onClick={handleCreateAndPublish}>
|
|
50
|
+
* I’m feeling lucky
|
|
51
|
+
* </button>
|
|
52
|
+
* )
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
12
55
|
export function useApplyActions(): <TDocument extends SanityDocument>(
|
|
13
56
|
action: DocumentAction<TDocument> | DocumentAction<TDocument>[],
|
|
14
57
|
options?: ApplyActionsOptions,
|
|
15
58
|
) => Promise<ActionsResult<TDocument>>
|
|
59
|
+
|
|
16
60
|
/** @beta */
|
|
17
61
|
export function useApplyActions(): (
|
|
18
62
|
action: DocumentAction | DocumentAction[],
|
|
@@ -10,16 +10,90 @@ import {useCallback, useMemo, useSyncExternalStore} from 'react'
|
|
|
10
10
|
|
|
11
11
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
12
12
|
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* @beta
|
|
15
|
+
*
|
|
16
|
+
* ## useDocument(doc, path)
|
|
17
|
+
* Read and subscribe to nested values in a document
|
|
18
|
+
* @category Documents
|
|
19
|
+
* @param doc - The document to read state from
|
|
20
|
+
* @param path - The path to the nested value to read from
|
|
21
|
+
* @returns The value at the specified path
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* import {type DocumentHandle, useDocument} from '@sanity/sdk-react'
|
|
25
|
+
*
|
|
26
|
+
* function OrderLink({documentHandle}: {documentHandle: DocumentHandle}) {
|
|
27
|
+
* const title = useDocument(documentHandle, 'title')
|
|
28
|
+
* const id = useDocument(documentHandle, '_id')
|
|
29
|
+
*
|
|
30
|
+
* return (
|
|
31
|
+
* <a href=`/order/${id}`>Order {title} today!</a>
|
|
32
|
+
* )
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
14
37
|
export function useDocument<
|
|
15
38
|
TDocument extends SanityDocument,
|
|
16
39
|
TPath extends JsonMatchPath<TDocument>,
|
|
17
40
|
>(doc: string | DocumentHandle<TDocument>, path: TPath): JsonMatch<TDocument, TPath> | undefined
|
|
18
|
-
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @beta
|
|
44
|
+
* ## useDocument(doc)
|
|
45
|
+
* Read and subscribe to an entire document
|
|
46
|
+
* @param doc - The document to read state from
|
|
47
|
+
* @returns The document state as an object
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* import {type SanityDocument, type DocumentHandle, useDocument} from '@sanity/sdk-react'
|
|
51
|
+
*
|
|
52
|
+
* interface Book extends SanityDocument {
|
|
53
|
+
* title: string
|
|
54
|
+
* author: string
|
|
55
|
+
* summary: string
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* function DocumentView({documentHandle}: {documentHandle: DocumentHandle}) {
|
|
59
|
+
* const book = useDocument<Book>(documentHandle)
|
|
60
|
+
*
|
|
61
|
+
* return (
|
|
62
|
+
* <article>
|
|
63
|
+
* <h1>{book?.title}</h1>
|
|
64
|
+
* <address>By {book?.author}</address>
|
|
65
|
+
*
|
|
66
|
+
* <h2>Summary</h2>
|
|
67
|
+
* {book?.summary}
|
|
68
|
+
*
|
|
69
|
+
* <h2>Order</h2>
|
|
70
|
+
* <a href=`/order/${book._id}`>Order {book?.title} today!</a>
|
|
71
|
+
* </article>
|
|
72
|
+
* )
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
*/
|
|
19
77
|
export function useDocument<TDocument extends SanityDocument>(
|
|
20
78
|
doc: string | DocumentHandle<TDocument>,
|
|
21
79
|
): TDocument | null
|
|
22
|
-
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @beta
|
|
83
|
+
* Reads and subscribes to a document’s realtime state, incorporating both local and remote changes.
|
|
84
|
+
* When called with a `path` argument, the hook will return the nested value’s state.
|
|
85
|
+
* When called without a `path` argument, the entire document’s state will be returned.
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* `useDocument` is designed to be used within a realtime context in which local updates to documents
|
|
89
|
+
* need to be displayed before they are persisted to the remote copy. This can be useful within a collaborative
|
|
90
|
+
* or realtime editing interface where local changes need to be reflected immediately.
|
|
91
|
+
*
|
|
92
|
+
* However, this hook can be too resource intensive for applications where static document values simply
|
|
93
|
+
* need to be displayed (or when changes to documents don’t need to be reflected immediately);
|
|
94
|
+
* consider using `usePreview` or `useQuery` for these use cases instead. These hooks leverage the Sanity
|
|
95
|
+
* Live Content API to provide a more efficient way to read and subscribe to document state.
|
|
96
|
+
*/
|
|
23
97
|
export function useDocument(doc: string | DocumentHandle, path?: string): unknown {
|
|
24
98
|
const documentId = typeof doc === 'string' ? doc : doc._id
|
|
25
99
|
const instance = useSanityInstance()
|
|
@@ -3,7 +3,29 @@ import {useCallback, useEffect, useInsertionEffect, useRef} from 'react'
|
|
|
3
3
|
|
|
4
4
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
5
5
|
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @beta
|
|
9
|
+
*
|
|
10
|
+
* Subscribes an event handler to events in your application’s document store, such as document
|
|
11
|
+
* creation, deletion, and updates.
|
|
12
|
+
*
|
|
13
|
+
* @category Documents
|
|
14
|
+
* @param handler - The event handler to register.
|
|
15
|
+
* @example
|
|
16
|
+
* ```
|
|
17
|
+
* import {useDocumentEvent} from '@sanity/sdk-react'
|
|
18
|
+
* import {type DocumentEvent} from '@sanity/sdk'
|
|
19
|
+
*
|
|
20
|
+
* useDocumentEvent((event) => {
|
|
21
|
+
* if (event.type === DocumentEvent.DocumentDeletedEvent) {
|
|
22
|
+
* alert(`Document with ID ${event.documentId} deleted!`)
|
|
23
|
+
* } else {
|
|
24
|
+
* console.log(event)
|
|
25
|
+
* }
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
7
29
|
export function useDocumentEvent(handler: (documentEvent: DocumentEvent) => void): void {
|
|
8
30
|
const ref = useRef(handler)
|
|
9
31
|
|
|
@@ -7,7 +7,7 @@ import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
|
7
7
|
vi.mock('../helpers/createStateSourceHook', () => ({createStateSourceHook: vi.fn(identity)}))
|
|
8
8
|
vi.mock('@sanity/sdk', () => ({getDocumentSyncStatus: vi.fn()}))
|
|
9
9
|
|
|
10
|
-
describe('
|
|
10
|
+
describe('useDocumentSyncStatus', () => {
|
|
11
11
|
it('calls `createStateSourceHook` with `getTokenState`', async () => {
|
|
12
12
|
const {useDocumentSyncStatus} = await import('./useDocumentSyncStatus')
|
|
13
13
|
expect(createStateSourceHook).toHaveBeenCalledWith(getDocumentSyncStatus)
|
|
@@ -1,6 +1,29 @@
|
|
|
1
|
-
import {getDocumentSyncStatus} from '@sanity/sdk'
|
|
1
|
+
import {type DocumentHandle, getDocumentSyncStatus} from '@sanity/sdk'
|
|
2
2
|
|
|
3
3
|
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
4
4
|
|
|
5
|
+
type UseDocumentSyncStatus = {
|
|
6
|
+
/**
|
|
7
|
+
* Exposes the document’s sync status between local and remote document states.
|
|
8
|
+
*
|
|
9
|
+
* @category Documents
|
|
10
|
+
* @param doc - The document handle to get sync status for
|
|
11
|
+
* @returns `true` if local changes are synced with remote, `false` if the changes are not synced, and `undefined` if the document is not found
|
|
12
|
+
* @example Disable a Save button when there are no changes to sync
|
|
13
|
+
* ```
|
|
14
|
+
* const myDocumentHandle = { _id: 'documentId', _type: 'documentType' }
|
|
15
|
+
* const documentSynced = useDocumentSyncStatus(myDocumentHandle)
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <button disabled={documentSynced}>
|
|
19
|
+
* Save Changes
|
|
20
|
+
* </button>
|
|
21
|
+
* )
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
(doc: DocumentHandle): boolean | undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
5
27
|
/** @beta */
|
|
6
|
-
export const useDocumentSyncStatus =
|
|
28
|
+
export const useDocumentSyncStatus: UseDocumentSyncStatus =
|
|
29
|
+
createStateSourceHook(getDocumentSyncStatus)
|
|
@@ -17,7 +17,52 @@ const ignoredKeys = ['_id', '_type', '_createdAt', '_updatedAt', '_rev']
|
|
|
17
17
|
|
|
18
18
|
type Updater<TValue> = TValue | ((nextValue: TValue) => TValue)
|
|
19
19
|
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @beta
|
|
23
|
+
*
|
|
24
|
+
* ## useEditDocument(doc, path)
|
|
25
|
+
* Edit a nested value within a document
|
|
26
|
+
*
|
|
27
|
+
* @category Documents
|
|
28
|
+
* @param doc - The document to be edited; either as a document handle or the document’s ID a string
|
|
29
|
+
* @param path - The path to the nested value to be edited
|
|
30
|
+
* @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.
|
|
31
|
+
* @example Update a document’s name by providing the new value directly
|
|
32
|
+
* ```
|
|
33
|
+
* const handle = { _id: 'documentId', _type: 'documentType' }
|
|
34
|
+
* const name = useDocument(handle, 'name')
|
|
35
|
+
* const editName = useEditDocument(handle, 'name')
|
|
36
|
+
*
|
|
37
|
+
* function handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
38
|
+
* editName(event.target.value)
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* return (
|
|
42
|
+
* <input type='text' value={name} onChange={handleNameChange} />
|
|
43
|
+
* )
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example Update a count on a document by providing an updater function
|
|
47
|
+
* ```
|
|
48
|
+
* const handle = { _id: 'documentId', _type: 'documentType' }
|
|
49
|
+
* const count = useDocument(handle, 'count')
|
|
50
|
+
* const editCount = useEditDocument(handle, 'count')
|
|
51
|
+
*
|
|
52
|
+
* function incrementCount() {
|
|
53
|
+
* editCount(previousCount => previousCount + 1)
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* return (
|
|
57
|
+
* <>
|
|
58
|
+
* <button onClick={incrementCount}>
|
|
59
|
+
* Increment
|
|
60
|
+
* </button>
|
|
61
|
+
* Current count: {count}
|
|
62
|
+
* </>
|
|
63
|
+
* )
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
21
66
|
export function useEditDocument<
|
|
22
67
|
TDocument extends SanityDocument,
|
|
23
68
|
TPath extends JsonMatchPath<TDocument>,
|
|
@@ -25,11 +70,81 @@ export function useEditDocument<
|
|
|
25
70
|
doc: string | DocumentHandle<TDocument>,
|
|
26
71
|
path: TPath,
|
|
27
72
|
): (nextValue: Updater<JsonMatch<TDocument, TPath>>) => Promise<ActionsResult<TDocument>>
|
|
28
|
-
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
*
|
|
76
|
+
* @beta
|
|
77
|
+
*
|
|
78
|
+
* ## useEditDocument(doc)
|
|
79
|
+
* Edit an entire document
|
|
80
|
+
* @param doc - The document to be edited; either as a document handle or the document’s ID a string
|
|
81
|
+
* @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.
|
|
82
|
+
* @example
|
|
83
|
+
* ```
|
|
84
|
+
* const myDocumentHandle = { _id: 'documentId', _type: 'documentType' }
|
|
85
|
+
*
|
|
86
|
+
* const myDocument = useDocument(myDocumentHandle)
|
|
87
|
+
* const { title, price } = myDocument
|
|
88
|
+
*
|
|
89
|
+
* const editMyDocument = useEditDocument(myDocumentHandle)
|
|
90
|
+
*
|
|
91
|
+
* function handleFieldChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
92
|
+
* const {name, value} = e.currentTarget
|
|
93
|
+
* // Use an updater function to update the document state based on the previous state
|
|
94
|
+
* editMyDocument(previousDocument => ({
|
|
95
|
+
* ...previousDocument,
|
|
96
|
+
* [name]: value
|
|
97
|
+
* }))
|
|
98
|
+
* }
|
|
99
|
+
*
|
|
100
|
+
* function handleSaleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
101
|
+
* const { checked } = e.currentTarget
|
|
102
|
+
* if (checked) {
|
|
103
|
+
* // Use an updater function to add a new salePrice field;
|
|
104
|
+
* // set it at a 20% discount off the normal price
|
|
105
|
+
* editMyDocument(previousDocument => ({
|
|
106
|
+
* ...previousDocument,
|
|
107
|
+
* salePrice: previousDocument.price * 0.8,
|
|
108
|
+
* }))
|
|
109
|
+
* } else {
|
|
110
|
+
* // Get the document state without the salePrice field
|
|
111
|
+
* const { salePrice, ...rest } = myDocument
|
|
112
|
+
* // Update the document state to remove the salePrice field
|
|
113
|
+
* editMyDocument(rest)
|
|
114
|
+
* }
|
|
115
|
+
* }
|
|
116
|
+
*
|
|
117
|
+
* return (
|
|
118
|
+
* <>
|
|
119
|
+
* <form onSubmit={e => e.preventDefault()}>
|
|
120
|
+
* <input name='title' type='text' value={title} onChange={handleFieldChange} />
|
|
121
|
+
* <input name='price' type='number' value={price} onChange={handleFieldChange} />
|
|
122
|
+
* <input
|
|
123
|
+
* name='salePrice'
|
|
124
|
+
* type='checkbox'
|
|
125
|
+
* checked={Object(myDocument).hasOwnProperty('salePrice')}
|
|
126
|
+
* onChange={handleSaleChange}
|
|
127
|
+
* />
|
|
128
|
+
* </form>
|
|
129
|
+
* <pre><code>
|
|
130
|
+
* {JSON.stringify(myDocument, null, 2)}
|
|
131
|
+
* </code></pre>
|
|
132
|
+
* </>
|
|
133
|
+
* )
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
29
136
|
export function useEditDocument<TDocument extends SanityDocument>(
|
|
30
137
|
doc: string | DocumentHandle<TDocument>,
|
|
31
138
|
): (nextValue: Updater<TDocument>) => Promise<ActionsResult<TDocument>>
|
|
32
|
-
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
*
|
|
142
|
+
* @beta
|
|
143
|
+
*
|
|
144
|
+
* Enables editing of a document’s state.
|
|
145
|
+
* When called with a `path` argument, the hook will return a function for updating a nested value.
|
|
146
|
+
* When called without a `path` argument, the hook will return a function for updating the entire document.
|
|
147
|
+
*/
|
|
33
148
|
export function useEditDocument(
|
|
34
149
|
doc: string | DocumentHandle,
|
|
35
150
|
path?: string,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {type DocumentAction, getPermissionsState, type PermissionsResult} from '@sanity/sdk'
|
|
2
|
+
import {useCallback, useMemo, useSyncExternalStore} from 'react'
|
|
3
|
+
import {filter, firstValueFrom} from 'rxjs'
|
|
4
|
+
|
|
5
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
|
|
7
|
+
/** @beta */
|
|
8
|
+
export function usePermissions(actions: DocumentAction | DocumentAction[]): PermissionsResult {
|
|
9
|
+
const instance = useSanityInstance()
|
|
10
|
+
const isDocumentReady = useCallback(
|
|
11
|
+
() => getPermissionsState(instance, actions).getCurrent() !== undefined,
|
|
12
|
+
[actions, instance],
|
|
13
|
+
)
|
|
14
|
+
if (!isDocumentReady()) {
|
|
15
|
+
throw firstValueFrom(
|
|
16
|
+
getPermissionsState(instance, actions).observable.pipe(
|
|
17
|
+
filter((result) => result !== undefined),
|
|
18
|
+
),
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const {subscribe, getCurrent} = useMemo(
|
|
23
|
+
() => getPermissionsState(instance, actions),
|
|
24
|
+
[actions, instance],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return useSyncExternalStore(subscribe, getCurrent) as PermissionsResult
|
|
28
|
+
}
|
|
@@ -5,8 +5,10 @@ import {useSanityInstance} from '../context/useSanityInstance'
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @public
|
|
8
|
+
* A live collection of {@link DocumentHandle}s, along with metadata about the collection and a function for loading more of them.
|
|
9
|
+
* @category Types
|
|
8
10
|
*/
|
|
9
|
-
export interface
|
|
11
|
+
export interface DocumentHandleCollection {
|
|
10
12
|
/** Retrieve more documents matching the provided options */
|
|
11
13
|
loadMore: () => void
|
|
12
14
|
/** The retrieved document handles of the documents matching the provided options */
|
|
@@ -31,13 +33,19 @@ const STABLE_EMPTY = {
|
|
|
31
33
|
/**
|
|
32
34
|
* @public
|
|
33
35
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
+
* Retrieves and provides access to a live collection of {@link DocumentHandle}s, with an optional filter and sort applied.
|
|
37
|
+
* The returned document handles are canonical — that is, they refer to the document in its current state, whether draft, published, or within a release or perspective.
|
|
38
|
+
* Because the returned document handle collection is live, the results will update in real time until the component invoking the hook is unmounted.
|
|
36
39
|
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* {@link DocumentHandle}s are used by many other hooks (such as {@link usePreview}, {@link useDocument}, and {@link useEditDocument})
|
|
42
|
+
* to work with documents in various ways without the entire document needing to be fetched upfront.
|
|
43
|
+
*
|
|
44
|
+
* @category Documents
|
|
37
45
|
* @param options - Options for narrowing and sorting the document collection
|
|
38
|
-
* @returns The collection of
|
|
46
|
+
* @returns The collection of document handles matching the provided options (if any), as well as properties describing the collection and a function to load more.
|
|
39
47
|
*
|
|
40
|
-
* @example Retrieving all documents of type 'movie'
|
|
48
|
+
* @example Retrieving document handles for all documents of type 'movie'
|
|
41
49
|
* ```
|
|
42
50
|
* const { results, isPending } = useDocuments({ filter: '_type == "movie"' })
|
|
43
51
|
*
|
|
@@ -54,7 +62,7 @@ const STABLE_EMPTY = {
|
|
|
54
62
|
* )
|
|
55
63
|
* ```
|
|
56
64
|
*
|
|
57
|
-
* @example Retrieving all movies released since 1980, sorted by director’s last name
|
|
65
|
+
* @example Retrieving document handles for all movies released since 1980, sorted by director’s last name
|
|
58
66
|
* ```
|
|
59
67
|
* const { results } = useDocuments({
|
|
60
68
|
* filter: '_type == "movie" && releaseDate >= "1980-01-01"',
|
|
@@ -79,7 +87,7 @@ const STABLE_EMPTY = {
|
|
|
79
87
|
* )
|
|
80
88
|
* ```
|
|
81
89
|
*/
|
|
82
|
-
export function useDocuments(options: DocumentListOptions = {}):
|
|
90
|
+
export function useDocuments(options: DocumentListOptions = {}): DocumentHandleCollection {
|
|
83
91
|
const instance = useSanityInstance()
|
|
84
92
|
|
|
85
93
|
// NOTE: useState is used because it guaranteed to return a stable reference
|
|
@@ -5,7 +5,8 @@ import {distinctUntilChanged, EMPTY, Observable, startWith, switchMap} from 'rxj
|
|
|
5
5
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @
|
|
8
|
+
* @beta
|
|
9
|
+
* @category Types
|
|
9
10
|
*/
|
|
10
11
|
export interface UsePreviewOptions {
|
|
11
12
|
document: DocumentHandle
|
|
@@ -13,7 +14,8 @@ export interface UsePreviewOptions {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* @
|
|
17
|
+
* @beta
|
|
18
|
+
* @category Types
|
|
17
19
|
*/
|
|
18
20
|
export interface UsePreviewResults {
|
|
19
21
|
/** The results of resolving the document’s preview values */
|
|
@@ -23,13 +25,14 @@ export interface UsePreviewResults {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
|
-
* @
|
|
28
|
+
* @beta
|
|
27
29
|
*
|
|
28
|
-
*
|
|
30
|
+
* Returns the preview values of a document (specified via a `DocumentHandle`),
|
|
29
31
|
* including the document’s `title`, `subtitle`, `media`, and `status`. These values are live and will update in realtime.
|
|
30
32
|
* To reduce unnecessary network requests for resolving the preview values, an optional `ref` can be passed to the hook so that preview
|
|
31
33
|
* resolution will only occur if the `ref` is intersecting the current viewport.
|
|
32
34
|
*
|
|
35
|
+
* @category Documents
|
|
33
36
|
* @param options - The document handle for the document you want to resolve preview values for, and an optional ref
|
|
34
37
|
* @returns The preview values for the given document and a boolean to indicate whether the resolution is pending
|
|
35
38
|
*
|