@sanity/sdk-react 2.14.0 → 2.15.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.
- package/README.md +18 -29
- package/dist/index.d.ts +459 -335
- package/dist/index.js +22 -6
- package/dist/index.js.map +1 -1
- package/package.json +29 -31
- package/src/_exports/sdk-react.ts +1 -0
- package/src/components/auth/AuthBoundary.recovery.test.tsx +86 -0
- package/src/components/auth/AuthBoundary.tsx +11 -1
- package/src/hooks/document/useCreateDocument.test.tsx +83 -0
- package/src/hooks/document/useCreateDocument.ts +117 -0
package/package.json
CHANGED
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK React toolkit for Content OS",
|
|
6
6
|
"keywords": [
|
|
7
|
-
"sanity",
|
|
8
|
-
"sdk",
|
|
9
|
-
"content operating system",
|
|
10
7
|
"cms",
|
|
8
|
+
"content",
|
|
9
|
+
"content operating system",
|
|
11
10
|
"headless",
|
|
12
11
|
"realtime",
|
|
13
|
-
"
|
|
12
|
+
"sanity",
|
|
13
|
+
"sdk"
|
|
14
14
|
],
|
|
15
15
|
"homepage": "https://github.com/sanity-io/sdk/tree/main/packages/react/README.md",
|
|
16
16
|
"bugs": {
|
|
17
17
|
"url": "https://github.com/sanity-io/sdk/issues"
|
|
18
18
|
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "Sanity <developers@sanity.io>",
|
|
19
21
|
"repository": {
|
|
20
22
|
"type": "git",
|
|
21
23
|
"url": "git+https://github.com/sanity-io/sdk.git",
|
|
22
24
|
"directory": "packages/react"
|
|
23
25
|
},
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
27
30
|
"type": "module",
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
28
35
|
"exports": {
|
|
29
36
|
".": {
|
|
30
37
|
"source": "./src/_exports/index.ts",
|
|
@@ -33,51 +40,44 @@
|
|
|
33
40
|
},
|
|
34
41
|
"./package.json": "./package.json"
|
|
35
42
|
},
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"files": [
|
|
40
|
-
"dist",
|
|
41
|
-
"src"
|
|
42
|
-
],
|
|
43
|
-
"browserslist": "extends @sanity/browserslist-config",
|
|
44
|
-
"prettier": "@sanity/prettier-config",
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@sanity/client": "^7.
|
|
47
|
+
"@sanity/client": "^7.23.0",
|
|
47
48
|
"@sanity/message-protocol": "^0.23.0",
|
|
48
|
-
"@sanity/types": "^6.
|
|
49
|
+
"@sanity/types": "^6.1.0",
|
|
49
50
|
"groq": "3.88.1-typegen-experimental.0",
|
|
50
51
|
"react-compiler-runtime": "19.1.0-rc.2",
|
|
51
52
|
"react-error-boundary": "^6.1.2",
|
|
52
53
|
"rxjs": "^7.8.2",
|
|
53
|
-
"@sanity/sdk": "2.
|
|
54
|
+
"@sanity/sdk": "2.15.0"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@sanity/browserslist-config": "^1.0.5",
|
|
57
58
|
"@sanity/comlink": "^4.0.1",
|
|
58
|
-
"@sanity/pkg-utils": "^10.5.
|
|
59
|
-
"@sanity/prettier-config": "^1.0.6",
|
|
59
|
+
"@sanity/pkg-utils": "^10.5.8",
|
|
60
60
|
"@testing-library/jest-dom": "^6.9.1",
|
|
61
61
|
"@testing-library/react": "^16.3.2",
|
|
62
62
|
"@types/node": "^24.12.4",
|
|
63
63
|
"@types/react": "^19.2.17",
|
|
64
64
|
"@types/react-dom": "^19.2.3",
|
|
65
65
|
"@vitejs/plugin-react": "^5.2.0",
|
|
66
|
-
"@vitest/coverage-v8": "^4.1.
|
|
66
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
67
67
|
"babel-plugin-react-compiler": "19.1.0-rc.1",
|
|
68
68
|
"eslint": "^9.39.4",
|
|
69
69
|
"groq-js": "^1.30.2",
|
|
70
70
|
"jsdom": "^29.1.1",
|
|
71
|
-
"
|
|
71
|
+
"oxfmt": "^0.55.0",
|
|
72
72
|
"react": "^19.2.7",
|
|
73
73
|
"react-dom": "^19.2.7",
|
|
74
|
-
"rollup-plugin-visualizer": "^
|
|
74
|
+
"rollup-plugin-visualizer": "^7.0.1",
|
|
75
75
|
"typescript": "^5.9.3",
|
|
76
76
|
"vite": "^7.3.5",
|
|
77
|
-
"vitest": "^4.1.
|
|
77
|
+
"vitest": "^4.1.9",
|
|
78
|
+
"@repo/package.bundle": "3.82.0",
|
|
78
79
|
"@repo/config-eslint": "0.0.0",
|
|
79
80
|
"@repo/config-test": "0.0.1",
|
|
80
|
-
"@repo/package.bundle": "3.82.0",
|
|
81
81
|
"@repo/package.config": "0.0.1",
|
|
82
82
|
"@repo/tsconfig": "0.0.1"
|
|
83
83
|
},
|
|
@@ -85,16 +85,14 @@
|
|
|
85
85
|
"react": "^18.0.0 || ^19.0.0",
|
|
86
86
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
87
87
|
},
|
|
88
|
-
"
|
|
89
|
-
"access": "public"
|
|
90
|
-
},
|
|
88
|
+
"browserslist": "extends @sanity/browserslist-config",
|
|
91
89
|
"scripts": {
|
|
92
90
|
"build": "pkg build --strict --clean --check",
|
|
93
91
|
"build:bundle": "vite build --configLoader runner --config package.bundle.ts",
|
|
94
92
|
"clean": "rimraf dist",
|
|
95
93
|
"dev": "pkg watch",
|
|
96
94
|
"docs": "typedoc --json docs/typedoc.json --tsconfig ./tsconfig.dist.json",
|
|
97
|
-
"format": "
|
|
95
|
+
"format": "oxfmt",
|
|
98
96
|
"lint": "eslint .",
|
|
99
97
|
"test": "vitest run",
|
|
100
98
|
"test:coverage": "vitest run --coverage",
|
|
@@ -59,6 +59,7 @@ export {useStudioWorkspacesByProjectIdDataset} from '../hooks/dashboard/useStudi
|
|
|
59
59
|
export {useWindowTitle} from '../hooks/dashboard/useWindowTitle'
|
|
60
60
|
export {useDatasets} from '../hooks/datasets/useDatasets'
|
|
61
61
|
export {useApplyDocumentActions} from '../hooks/document/useApplyDocumentActions'
|
|
62
|
+
export {type CreateDocumentOverrides, useCreateDocument} from '../hooks/document/useCreateDocument'
|
|
62
63
|
export {useDocument} from '../hooks/document/useDocument'
|
|
63
64
|
export {useDocumentEvent} from '../hooks/document/useDocumentEvent'
|
|
64
65
|
export {useDocumentPermissions} from '../hooks/document/useDocumentPermissions'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {AuthStateType, getIsInDashboardState} from '@sanity/sdk'
|
|
2
|
+
import {render, screen, waitFor} from '@testing-library/react'
|
|
3
|
+
import {beforeEach, describe, expect, it, type Mock, vi} from 'vitest'
|
|
4
|
+
|
|
5
|
+
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
6
|
+
import {useAuthState} from '../../hooks/auth/useAuthState'
|
|
7
|
+
import {AuthBoundary} from './AuthBoundary'
|
|
8
|
+
|
|
9
|
+
// NOTE: unlike AuthBoundary.test.tsx this file does NOT mock react-error-boundary
|
|
10
|
+
// — we want the REAL ErrorBoundary so we can observe whether it recovers when the
|
|
11
|
+
// auth store transitions back to LOGGED_IN (e.g. after a successful silent token
|
|
12
|
+
// refresh via ComlinkTokenRefresh -> setAuthToken).
|
|
13
|
+
|
|
14
|
+
vi.mock('@sanity/sdk', async () => {
|
|
15
|
+
const actual = await vi.importActual('@sanity/sdk')
|
|
16
|
+
return {
|
|
17
|
+
...actual,
|
|
18
|
+
getIsInDashboardState: vi.fn(() => ({getCurrent: () => true})),
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
vi.mock('../../hooks/auth/useAuthState', () => ({useAuthState: vi.fn()}))
|
|
23
|
+
vi.mock('../../hooks/auth/useLoginUrl', () => ({
|
|
24
|
+
useLoginUrl: vi.fn(() => 'https://example.com/login'),
|
|
25
|
+
}))
|
|
26
|
+
vi.mock('../../hooks/auth/useVerifyOrgProjects', () => ({useVerifyOrgProjects: vi.fn(() => null)}))
|
|
27
|
+
vi.mock('../../hooks/auth/useLogOut', () => ({useLogOut: vi.fn(() => async () => {})}))
|
|
28
|
+
vi.mock('../../hooks/auth/useHandleAuthCallback', () => ({
|
|
29
|
+
useHandleAuthCallback: vi.fn(() => async () => {}),
|
|
30
|
+
}))
|
|
31
|
+
vi.mock('../../hooks/comlink/useWindowConnection', () => ({
|
|
32
|
+
useWindowConnection: vi.fn(() => ({fetch: vi.fn().mockResolvedValue({token: 'fresh-token'})})),
|
|
33
|
+
}))
|
|
34
|
+
// Avoid injecting the SanityOS bridge.js <script> during import.
|
|
35
|
+
vi.mock('../utils', () => ({isInIframe: vi.fn(() => false)}))
|
|
36
|
+
|
|
37
|
+
const mockUseAuthState = useAuthState as Mock
|
|
38
|
+
|
|
39
|
+
describe('AuthBoundary — recovery after silent token refresh (dashboard)', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
vi.clearAllMocks()
|
|
42
|
+
;(getIsInDashboardState as Mock).mockReturnValue({getCurrent: () => true})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('renders the app again once the session is re-established (LOGGED_IN)', async () => {
|
|
46
|
+
// 1. Session expires: a request 401s and the auth store goes to ERROR.
|
|
47
|
+
mockUseAuthState.mockReturnValue({
|
|
48
|
+
type: AuthStateType.ERROR,
|
|
49
|
+
error: Object.assign(new Error('Unauthorized'), {statusCode: 401}),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const {rerender} = render(
|
|
53
|
+
<ResourceProvider projectId="p" dataset="d" fallback={null}>
|
|
54
|
+
<AuthBoundary projectIds={['p']}>
|
|
55
|
+
<div>Protected Content</div>
|
|
56
|
+
</AuthBoundary>
|
|
57
|
+
</ResourceProvider>,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
// The error boundary catches the AuthError and shows the auth-error screen.
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
expect(screen.getByText('Authentication Error')).toBeInTheDocument()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// 2. ComlinkTokenRefresh fetches a fresh token from the Dashboard, setAuthToken
|
|
66
|
+
// lands it, /users/me re-fetches and the store returns to LOGGED_IN.
|
|
67
|
+
mockUseAuthState.mockReturnValue({
|
|
68
|
+
type: AuthStateType.LOGGED_IN,
|
|
69
|
+
currentUser: null,
|
|
70
|
+
token: 'fresh-token',
|
|
71
|
+
})
|
|
72
|
+
rerender(
|
|
73
|
+
<ResourceProvider projectId="p" dataset="d" fallback={null}>
|
|
74
|
+
<AuthBoundary projectIds={['p']}>
|
|
75
|
+
<div>Protected Content</div>
|
|
76
|
+
</AuthBoundary>
|
|
77
|
+
</ResourceProvider>,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
// EXPECTED (desired) behaviour: the app recovers automatically, no Retry click.
|
|
81
|
+
await waitFor(() => {
|
|
82
|
+
expect(screen.getByText('Protected Content')).toBeInTheDocument()
|
|
83
|
+
})
|
|
84
|
+
expect(screen.queryByText('Authentication Error')).not.toBeInTheDocument()
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -109,6 +109,16 @@ export function AuthBoundary({
|
|
|
109
109
|
LoginErrorComponent = LoginError,
|
|
110
110
|
...props
|
|
111
111
|
}: AuthBoundaryProps): React.ReactNode {
|
|
112
|
+
/**
|
|
113
|
+
* When the session is re-established (e.g. ComlinkTokenRefresh silently mints a
|
|
114
|
+
* fresh token via setAuthToken), the auth store returns to LOGGED_IN but the
|
|
115
|
+
* ErrorBoundary stays latched on its fallback until reset. Keying it on the
|
|
116
|
+
* recovered session lets it clear automatically
|
|
117
|
+
*/
|
|
118
|
+
const authState = useAuthState()
|
|
119
|
+
const sessionResetKey =
|
|
120
|
+
authState.type === AuthStateType.LOGGED_IN ? authState.token : authState.type
|
|
121
|
+
|
|
112
122
|
const FallbackComponent = useMemo(() => {
|
|
113
123
|
return function LoginComponentWithLayoutProps(fallbackProps: FallbackProps) {
|
|
114
124
|
// Chunk-load errors from any lazy-loaded code beneath this boundary
|
|
@@ -131,7 +141,7 @@ export function AuthBoundary({
|
|
|
131
141
|
|
|
132
142
|
return (
|
|
133
143
|
<ComlinkTokenRefreshProvider>
|
|
134
|
-
<ErrorBoundary FallbackComponent={FallbackComponent}>
|
|
144
|
+
<ErrorBoundary FallbackComponent={FallbackComponent} resetKeys={[sessionResetKey]}>
|
|
135
145
|
<AuthSwitch {...props} />
|
|
136
146
|
</ErrorBoundary>
|
|
137
147
|
</ComlinkTokenRefreshProvider>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {createDocument} from '@sanity/sdk'
|
|
2
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {renderHook} from '../../../test/test-utils'
|
|
5
|
+
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
6
|
+
import {useCreateDocument} from './useCreateDocument'
|
|
7
|
+
|
|
8
|
+
vi.mock('./useApplyDocumentActions', () => ({
|
|
9
|
+
useApplyDocumentActions: vi.fn(),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
const typeHandle = {
|
|
13
|
+
documentType: 'book',
|
|
14
|
+
projectId: 'test',
|
|
15
|
+
dataset: 'test',
|
|
16
|
+
} as const
|
|
17
|
+
|
|
18
|
+
describe('useCreateDocument hook', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.clearAllMocks()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.restoreAllMocks()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('applies a createDocument action with a generated id and initial values', async () => {
|
|
28
|
+
vi.spyOn(crypto, 'randomUUID').mockReturnValue('00000000-0000-0000-0000-000000000000')
|
|
29
|
+
const apply = vi.fn().mockResolvedValue({transactionId: 'tx1'})
|
|
30
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
31
|
+
|
|
32
|
+
const {result} = renderHook(() => useCreateDocument(typeHandle))
|
|
33
|
+
const handle = await result.current({title: 'New Book'})
|
|
34
|
+
|
|
35
|
+
expect(apply).toHaveBeenCalledWith(
|
|
36
|
+
createDocument(
|
|
37
|
+
{...typeHandle, documentId: '00000000-0000-0000-0000-000000000000'},
|
|
38
|
+
{
|
|
39
|
+
title: 'New Book',
|
|
40
|
+
},
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
expect(handle).toEqual({...typeHandle, documentId: '00000000-0000-0000-0000-000000000000'})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('returns a handle carrying the generated id', async () => {
|
|
47
|
+
vi.spyOn(crypto, 'randomUUID').mockReturnValue('11111111-1111-1111-1111-111111111111')
|
|
48
|
+
const apply = vi.fn().mockResolvedValue({transactionId: 'tx2'})
|
|
49
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
50
|
+
|
|
51
|
+
const {result} = renderHook(() => useCreateDocument(typeHandle))
|
|
52
|
+
const handle = await result.current()
|
|
53
|
+
|
|
54
|
+
expect(handle.documentId).toBe('11111111-1111-1111-1111-111111111111')
|
|
55
|
+
expect(handle.documentType).toBe('book')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('uses the documentId supplied on the handle instead of generating one', async () => {
|
|
59
|
+
const apply = vi.fn().mockResolvedValue({transactionId: 'tx3'})
|
|
60
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
61
|
+
|
|
62
|
+
const {result} = renderHook(() => useCreateDocument({...typeHandle, documentId: 'fixed-id'}))
|
|
63
|
+
const handle = await result.current()
|
|
64
|
+
|
|
65
|
+
expect(handle.documentId).toBe('fixed-id')
|
|
66
|
+
expect(apply).toHaveBeenCalledWith(
|
|
67
|
+
createDocument({...typeHandle, documentId: 'fixed-id'}, undefined),
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('uses a per-call documentId override over the handle id', async () => {
|
|
72
|
+
const apply = vi.fn().mockResolvedValue({transactionId: 'tx4'})
|
|
73
|
+
vi.mocked(useApplyDocumentActions).mockReturnValue(apply)
|
|
74
|
+
|
|
75
|
+
const {result} = renderHook(() => useCreateDocument({...typeHandle, documentId: 'handle-id'}))
|
|
76
|
+
const handle = await result.current({title: 'Override'}, {documentId: 'override-id'})
|
|
77
|
+
|
|
78
|
+
expect(handle.documentId).toBe('override-id')
|
|
79
|
+
expect(apply).toHaveBeenCalledWith(
|
|
80
|
+
createDocument({...typeHandle, documentId: 'override-id'}, {title: 'Override'}),
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {createDocument} from '@sanity/sdk'
|
|
2
|
+
import {type SanityDocument} from 'groq'
|
|
3
|
+
|
|
4
|
+
import {type DocumentHandle, type DocumentTypeHandle} from '../../config/handles'
|
|
5
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
6
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
7
|
+
import {useApplyDocumentActions} from './useApplyDocumentActions'
|
|
8
|
+
|
|
9
|
+
type IgnoredKey = '_id' | '_type' | '_rev' | '_createdAt' | '_updatedAt'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Optional per-call overrides for {@link useCreateDocument}'s create function.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export interface CreateDocumentOverrides {
|
|
16
|
+
/**
|
|
17
|
+
* Use this document ID instead of generating one. Overrides any `documentId`
|
|
18
|
+
* supplied on the handle passed to `useCreateDocument`.
|
|
19
|
+
*/
|
|
20
|
+
documentId?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Overload 1: Typegen — infers the document shape from your schema.
|
|
24
|
+
/**
|
|
25
|
+
* @public
|
|
26
|
+
* Create a new document, relying on Typegen for the initial-value type.
|
|
27
|
+
*
|
|
28
|
+
* @param options - A document-type handle including `documentType`, an optional `documentId`, and optionally `projectId`/`dataset`/`perspective`.
|
|
29
|
+
* @returns A function that creates the document. It accepts optional initial field values and an optional `{documentId}` override,
|
|
30
|
+
* and resolves to the {@link DocumentHandle} of the created document (carrying the generated or supplied id).
|
|
31
|
+
*/
|
|
32
|
+
export function useCreateDocument<
|
|
33
|
+
TDocumentType extends string = string,
|
|
34
|
+
TDataset extends string = string,
|
|
35
|
+
TProjectId extends string = string,
|
|
36
|
+
>(
|
|
37
|
+
options: DocumentTypeHandle<TDocumentType, TDataset, TProjectId>,
|
|
38
|
+
): (
|
|
39
|
+
initialValue?: Partial<
|
|
40
|
+
Omit<SanityDocument<TDocumentType, `${TProjectId}.${TDataset}`>, IgnoredKey>
|
|
41
|
+
>,
|
|
42
|
+
overrides?: CreateDocumentOverrides,
|
|
43
|
+
) => Promise<DocumentHandle<TDocumentType, TDataset, TProjectId>>
|
|
44
|
+
|
|
45
|
+
// Overload 2: Explicit type `TData`.
|
|
46
|
+
/**
|
|
47
|
+
* @public
|
|
48
|
+
* Create a new document with an explicit type `TData`.
|
|
49
|
+
*
|
|
50
|
+
* @param options - A document-type handle including `documentType` and optionally `projectId`/`dataset`/`perspective`.
|
|
51
|
+
* @returns A function that creates the document. It accepts optional initial field values (typed against `TData`) and an
|
|
52
|
+
* optional `{documentId}` override, and resolves to the {@link DocumentHandle} of the created document.
|
|
53
|
+
*/
|
|
54
|
+
export function useCreateDocument<TData extends Record<string, unknown>>(
|
|
55
|
+
options: DocumentTypeHandle,
|
|
56
|
+
): (
|
|
57
|
+
initialValue?: Partial<Omit<TData, IgnoredKey>>,
|
|
58
|
+
overrides?: CreateDocumentOverrides,
|
|
59
|
+
) => Promise<DocumentHandle>
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @public
|
|
63
|
+
* Provides a function to create a new document and returns its handle.
|
|
64
|
+
*
|
|
65
|
+
* @category Documents
|
|
66
|
+
* @remarks
|
|
67
|
+
* This is the create counterpart to {@link useEditDocument}. It wraps
|
|
68
|
+
* {@link useApplyDocumentActions} and the `createDocument` action for the common
|
|
69
|
+
* single-document case, so you don't have to assemble the action by hand.
|
|
70
|
+
*
|
|
71
|
+
* It handles the document ID for you: if you don't supply one (on the handle or
|
|
72
|
+
* via the per-call `{documentId}` override), a UUID is generated. Either way the
|
|
73
|
+
* returned {@link DocumentHandle} carries that id, ready to pass to
|
|
74
|
+
* {@link useDocument}, {@link useEditDocument}, or your router.
|
|
75
|
+
*
|
|
76
|
+
* Unlike {@link useEditDocument}, this hook does not read existing document state,
|
|
77
|
+
* so it never suspends.
|
|
78
|
+
*
|
|
79
|
+
* For atomic create-and-publish, or for creating several documents in a single
|
|
80
|
+
* transaction, use {@link useApplyDocumentActions} with the `createDocument` and
|
|
81
|
+
* `publishDocument` action creators directly.
|
|
82
|
+
*
|
|
83
|
+
* @example Create a document and navigate to it
|
|
84
|
+
* ```tsx
|
|
85
|
+
* import {useCreateDocument} from '@sanity/sdk-react'
|
|
86
|
+
* import {useNavigate} from 'react-router-dom'
|
|
87
|
+
*
|
|
88
|
+
* function CreateArticleButton() {
|
|
89
|
+
* const createArticle = useCreateDocument({documentType: 'article'})
|
|
90
|
+
* const navigate = useNavigate()
|
|
91
|
+
*
|
|
92
|
+
* const handleClick = async () => {
|
|
93
|
+
* const handle = await createArticle({title: 'New Article'})
|
|
94
|
+
* navigate(`/articles/${handle.documentId}`)
|
|
95
|
+
* }
|
|
96
|
+
*
|
|
97
|
+
* return <button onClick={handleClick}>Create Article</button>
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function useCreateDocument(
|
|
102
|
+
options: DocumentTypeHandle,
|
|
103
|
+
): (
|
|
104
|
+
initialValue?: Record<string, unknown>,
|
|
105
|
+
overrides?: CreateDocumentOverrides,
|
|
106
|
+
) => Promise<DocumentHandle> {
|
|
107
|
+
const instance = useSanityInstance()
|
|
108
|
+
trackHookUsage(instance, 'useCreateDocument')
|
|
109
|
+
const apply = useApplyDocumentActions()
|
|
110
|
+
|
|
111
|
+
return async (initialValue, overrides) => {
|
|
112
|
+
const documentId = overrides?.documentId ?? options.documentId ?? crypto.randomUUID()
|
|
113
|
+
const handle: DocumentHandle = {...options, documentId}
|
|
114
|
+
await apply(createDocument(handle, initialValue))
|
|
115
|
+
return handle
|
|
116
|
+
}
|
|
117
|
+
}
|