Skip to content

Commit 9e85be0

Browse files
authored
fix(next): autosave document rendering (#9364)
Closes #9242 and #9365. Autosave-enabled documents rendered within a drawer were not being properly handled. This was causing multiple draft documents to be created upon opening the drawer, as well as an empty document returned from the server function, etc.
1 parent 4030e21 commit 9e85be0

File tree

7 files changed

+82
-36
lines changed

7 files changed

+82
-36
lines changed

packages/next/src/views/Document/index.tsx

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const renderDocument = async ({
4949
}> => {
5050
const {
5151
collectionConfig,
52-
docID: id,
52+
docID: idFromArgs,
5353
globalConfig,
5454
locale,
5555
permissions,
@@ -72,7 +72,7 @@ export const renderDocument = async ({
7272
const segments = Array.isArray(params?.segments) ? params.segments : []
7373
const collectionSlug = collectionConfig?.slug || undefined
7474
const globalSlug = globalConfig?.slug || undefined
75-
const isEditing = getIsEditing({ id, collectionSlug, globalSlug })
75+
let isEditing = getIsEditing({ id: idFromArgs, collectionSlug, globalSlug })
7676

7777
let RootViewOverride: PayloadComponent
7878
let CustomView: ViewFromConfig<ServerSideEditViewProps>
@@ -82,10 +82,10 @@ export const renderDocument = async ({
8282
let apiURL: string
8383

8484
// Fetch the doc required for the view
85-
const doc =
85+
let doc =
8686
initialData ||
8787
(await getDocumentData({
88-
id,
88+
id: idFromArgs,
8989
collectionSlug,
9090
globalSlug,
9191
locale,
@@ -104,7 +104,7 @@ export const renderDocument = async ({
104104
] = await Promise.all([
105105
// Get document preferences
106106
getDocPreferences({
107-
id,
107+
id: idFromArgs,
108108
collectionSlug,
109109
globalSlug,
110110
payload,
@@ -113,7 +113,7 @@ export const renderDocument = async ({
113113

114114
// Get permissions
115115
getDocumentPermissions({
116-
id,
116+
id: idFromArgs,
117117
collectionConfig,
118118
data: doc,
119119
globalConfig,
@@ -122,7 +122,7 @@ export const renderDocument = async ({
122122

123123
// Fetch document lock state
124124
getIsLocked({
125-
id,
125+
id: idFromArgs,
126126
collectionConfig,
127127
globalConfig,
128128
isEditing,
@@ -135,7 +135,7 @@ export const renderDocument = async ({
135135
{ state: formState },
136136
] = await Promise.all([
137137
getVersions({
138-
id,
138+
id: idFromArgs,
139139
collectionConfig,
140140
docPermissions,
141141
globalConfig,
@@ -144,15 +144,15 @@ export const renderDocument = async ({
144144
user,
145145
}),
146146
buildFormState({
147-
id,
147+
id: idFromArgs,
148148
collectionSlug,
149149
data: doc,
150150
docPermissions,
151151
docPreferences,
152152
fallbackLocale: false,
153153
globalSlug,
154154
locale: locale?.code,
155-
operation: (collectionSlug && id) || globalSlug ? 'update' : 'create',
155+
operation: (collectionSlug && idFromArgs) || globalSlug ? 'update' : 'create',
156156
renderAllFields: true,
157157
req,
158158
schemaPath: collectionSlug || globalSlug,
@@ -187,7 +187,7 @@ export const renderDocument = async ({
187187

188188
const apiQueryParams = `?${params.toString()}`
189189

190-
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}${apiQueryParams}`
190+
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${idFromArgs}${apiQueryParams}`
191191

192192
RootViewOverride =
193193
collectionConfig?.admin?.components?.views?.edit?.root &&
@@ -274,8 +274,10 @@ export const renderDocument = async ({
274274
const validateDraftData =
275275
collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.validate
276276

277-
if (shouldAutosave && !validateDraftData && !id && collectionSlug) {
278-
const doc = await payload.create({
277+
let id = idFromArgs
278+
279+
if (shouldAutosave && !validateDraftData && !idFromArgs && collectionSlug) {
280+
doc = await payload.create({
279281
collection: collectionSlug,
280282
data: initialData || {},
281283
depth: 0,
@@ -287,12 +289,18 @@ export const renderDocument = async ({
287289
})
288290

289291
if (doc?.id) {
290-
const redirectURL = formatAdminURL({
291-
adminRoute,
292-
path: `/collections/${collectionSlug}/${doc.id}`,
293-
serverURL,
294-
})
295-
redirect(redirectURL)
292+
id = doc.id
293+
isEditing = getIsEditing({ id: doc.id, collectionSlug, globalSlug })
294+
295+
if (!drawerSlug) {
296+
const redirectURL = formatAdminURL({
297+
adminRoute,
298+
path: `/collections/${collectionSlug}/${doc.id}`,
299+
serverURL,
300+
})
301+
302+
redirect(redirectURL)
303+
}
296304
} else {
297305
throw new Error('not-found')
298306
}

packages/ui/src/elements/DocumentDrawer/DrawerContent.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22

33
import { useModal } from '@faceless-ui/modal'
4-
import React, { useCallback, useEffect, useState } from 'react'
4+
import React, { useCallback, useEffect, useRef, useState } from 'react'
55
import { toast } from 'sonner'
66

77
import type { DocumentDrawerProps } from './types.js'
@@ -45,6 +45,7 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
4545

4646
const [DocumentView, setDocumentView] = useState<React.ReactNode>(undefined)
4747
const [isLoading, setIsLoading] = useState(true)
48+
const hasRenderedDocument = useRef(false)
4849

4950
const getDocumentView = useCallback(
5051
(docID?: number | string) => {
@@ -142,8 +143,9 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
142143
}, [getDocumentView])
143144

144145
useEffect(() => {
145-
if (!DocumentView) {
146+
if (!DocumentView && !hasRenderedDocument.current) {
146147
getDocumentView(existingDocID)
148+
hasRenderedDocument.current = true
147149
}
148150
}, [DocumentView, getDocumentView, existingDocID])
149151

packages/ui/src/providers/ServerFunctions/index.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type RenderDocument = (args: {
3232
redirectAfterDelete?: boolean
3333
redirectAfterDuplicate?: boolean
3434
signal?: AbortSignal
35-
}) => Promise<{ docID: string; Document: React.ReactNode }>
35+
}) => Promise<{ data: Data; Document: React.ReactNode }>
3636

3737
type GetDocumentSlots = (args: {
3838
collectionSlug: string
@@ -129,16 +129,12 @@ export const ServerFunctionsProvider: React.FC<{
129129
const { signal: remoteSignal, ...rest } = args || {}
130130

131131
try {
132-
if (!remoteSignal?.aborted) {
133-
const result = (await serverFunction({
134-
name: 'render-document',
135-
args: { fallbackLocale: false, ...rest },
136-
})) as { docID: string; Document: React.ReactNode }
132+
const result = (await serverFunction({
133+
name: 'render-document',
134+
args: { fallbackLocale: false, ...rest },
135+
})) as { data: Data; Document: React.ReactNode }
137136

138-
if (!remoteSignal?.aborted) {
139-
return result
140-
}
141-
}
137+
return result
142138
} catch (_err) {
143139
console.error(_err) // eslint-disable-line no-console
144140
}

test/fields-relationship/e2e.spec.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,9 @@ describe('fields - relationship', () => {
179179
await expect(options).toHaveCount(2) // two docs
180180
await options.nth(0).click()
181181
await expect(field).toContainText(relationOneDoc.id)
182-
await saveDocAndAssert(page)
183-
await wait(200)
184-
await trackNetworkRequests(page, `/api/${relationOneSlug}`, {
185-
beforePoll: async () => await page.reload(),
182+
await trackNetworkRequests(page, `/api/${relationOneSlug}`, async () => {
183+
await saveDocAndAssert(page)
184+
await wait(200)
186185
})
187186
})
188187

test/helpers/e2e/trackNetworkRequests.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { expect } from '@playwright/test'
88
export const trackNetworkRequests = async (
99
page: Page,
1010
url: string,
11+
action: () => Promise<any>,
1112
options?: {
1213
allowedNumberOfRequests?: number
1314
beforePoll?: () => Promise<any> | void
@@ -26,6 +27,8 @@ export const trackNetworkRequests = async (
2627
}
2728
})
2829

30+
await action()
31+
2932
if (typeof beforePoll === 'function') {
3033
await beforePoll()
3134
}

test/versions/e2e.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import type { BrowserContext, Page } from '@playwright/test'
2626

2727
import { expect, test } from '@playwright/test'
28+
import { navigateToDoc } from 'helpers/e2e/navigateToDoc.js'
2829
import path from 'path'
2930
import { wait } from 'payload/shared'
3031
import { fileURLToPath } from 'url'
@@ -43,6 +44,7 @@ import {
4344
throttleTest,
4445
} from '../helpers.js'
4546
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
47+
import { trackNetworkRequests } from '../helpers/e2e/trackNetworkRequests.js'
4648
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
4749
import { reInitializeDB } from '../helpers/reInitializeDB.js'
4850
import { waitForAutoSaveToRunAndComplete } from '../helpers/waitForAutoSaveToRunAndComplete.js'
@@ -394,6 +396,42 @@ describe('versions', () => {
394396
expect(page.url()).toMatch(/\/versions$/)
395397
})
396398

399+
test('collection - should autosave', async () => {
400+
await page.goto(autosaveURL.create)
401+
await page.locator('#field-title').fill('autosave title')
402+
await waitForAutoSaveToRunAndComplete(page)
403+
await expect(page.locator('#field-title')).toHaveValue('autosave title')
404+
405+
const { id: postID } = await payload.create({
406+
collection: postCollectionSlug,
407+
data: {
408+
title: 'post title',
409+
description: 'post description',
410+
},
411+
})
412+
413+
await page.goto(postURL.edit(postID))
414+
415+
await trackNetworkRequests(
416+
page,
417+
`${serverURL}/admin/collections/${postCollectionSlug}/${postID}`,
418+
async () => {
419+
await page
420+
.locator(
421+
'#field-relationToAutosaves.field-type.relationship .relationship-add-new__add-button.doc-drawer__toggler',
422+
)
423+
.click()
424+
},
425+
{
426+
allowedNumberOfRequests: 1,
427+
},
428+
)
429+
430+
const drawer = page.locator('[id^=doc-drawer_autosave-posts_1_]')
431+
await expect(drawer).toBeVisible()
432+
await expect(drawer.locator('.id-label')).toBeVisible()
433+
})
434+
397435
test('global - should autosave', async () => {
398436
const url = new AdminUrlUtil(serverURL, autoSaveGlobalSlug)
399437
await page.goto(url.global(autoSaveGlobalSlug))

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
],
3838
"paths": {
3939
"@payload-config": [
40-
"./test/_community/config.ts"
40+
"./test/versions/config.ts"
4141
],
4242
"@payloadcms/live-preview": [
4343
"./packages/live-preview/src"

0 commit comments

Comments
 (0)