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.
This commit is contained in:
@@ -49,7 +49,7 @@ export const renderDocument = async ({
|
||||
}> => {
|
||||
const {
|
||||
collectionConfig,
|
||||
docID: id,
|
||||
docID: idFromArgs,
|
||||
globalConfig,
|
||||
locale,
|
||||
permissions,
|
||||
@@ -72,7 +72,7 @@ export const renderDocument = async ({
|
||||
const segments = Array.isArray(params?.segments) ? params.segments : []
|
||||
const collectionSlug = collectionConfig?.slug || undefined
|
||||
const globalSlug = globalConfig?.slug || undefined
|
||||
const isEditing = getIsEditing({ id, collectionSlug, globalSlug })
|
||||
let isEditing = getIsEditing({ id: idFromArgs, collectionSlug, globalSlug })
|
||||
|
||||
let RootViewOverride: PayloadComponent
|
||||
let CustomView: ViewFromConfig<ServerSideEditViewProps>
|
||||
@@ -82,10 +82,10 @@ export const renderDocument = async ({
|
||||
let apiURL: string
|
||||
|
||||
// Fetch the doc required for the view
|
||||
const doc =
|
||||
let doc =
|
||||
initialData ||
|
||||
(await getDocumentData({
|
||||
id,
|
||||
id: idFromArgs,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
locale,
|
||||
@@ -104,7 +104,7 @@ export const renderDocument = async ({
|
||||
] = await Promise.all([
|
||||
// Get document preferences
|
||||
getDocPreferences({
|
||||
id,
|
||||
id: idFromArgs,
|
||||
collectionSlug,
|
||||
globalSlug,
|
||||
payload,
|
||||
@@ -113,7 +113,7 @@ export const renderDocument = async ({
|
||||
|
||||
// Get permissions
|
||||
getDocumentPermissions({
|
||||
id,
|
||||
id: idFromArgs,
|
||||
collectionConfig,
|
||||
data: doc,
|
||||
globalConfig,
|
||||
@@ -122,7 +122,7 @@ export const renderDocument = async ({
|
||||
|
||||
// Fetch document lock state
|
||||
getIsLocked({
|
||||
id,
|
||||
id: idFromArgs,
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
isEditing,
|
||||
@@ -135,7 +135,7 @@ export const renderDocument = async ({
|
||||
{ state: formState },
|
||||
] = await Promise.all([
|
||||
getVersions({
|
||||
id,
|
||||
id: idFromArgs,
|
||||
collectionConfig,
|
||||
docPermissions,
|
||||
globalConfig,
|
||||
@@ -144,7 +144,7 @@ export const renderDocument = async ({
|
||||
user,
|
||||
}),
|
||||
buildFormState({
|
||||
id,
|
||||
id: idFromArgs,
|
||||
collectionSlug,
|
||||
data: doc,
|
||||
docPermissions,
|
||||
@@ -152,7 +152,7 @@ export const renderDocument = async ({
|
||||
fallbackLocale: false,
|
||||
globalSlug,
|
||||
locale: locale?.code,
|
||||
operation: (collectionSlug && id) || globalSlug ? 'update' : 'create',
|
||||
operation: (collectionSlug && idFromArgs) || globalSlug ? 'update' : 'create',
|
||||
renderAllFields: true,
|
||||
req,
|
||||
schemaPath: collectionSlug || globalSlug,
|
||||
@@ -187,7 +187,7 @@ export const renderDocument = async ({
|
||||
|
||||
const apiQueryParams = `?${params.toString()}`
|
||||
|
||||
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}${apiQueryParams}`
|
||||
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${idFromArgs}${apiQueryParams}`
|
||||
|
||||
RootViewOverride =
|
||||
collectionConfig?.admin?.components?.views?.edit?.root &&
|
||||
@@ -274,8 +274,10 @@ export const renderDocument = async ({
|
||||
const validateDraftData =
|
||||
collectionConfig?.versions?.drafts && collectionConfig?.versions?.drafts?.validate
|
||||
|
||||
if (shouldAutosave && !validateDraftData && !id && collectionSlug) {
|
||||
const doc = await payload.create({
|
||||
let id = idFromArgs
|
||||
|
||||
if (shouldAutosave && !validateDraftData && !idFromArgs && collectionSlug) {
|
||||
doc = await payload.create({
|
||||
collection: collectionSlug,
|
||||
data: initialData || {},
|
||||
depth: 0,
|
||||
@@ -287,12 +289,18 @@ export const renderDocument = async ({
|
||||
})
|
||||
|
||||
if (doc?.id) {
|
||||
id = doc.id
|
||||
isEditing = getIsEditing({ id: doc.id, collectionSlug, globalSlug })
|
||||
|
||||
if (!drawerSlug) {
|
||||
const redirectURL = formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/collections/${collectionSlug}/${doc.id}`,
|
||||
serverURL,
|
||||
})
|
||||
|
||||
redirect(redirectURL)
|
||||
}
|
||||
} else {
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { DocumentDrawerProps } from './types.js'
|
||||
@@ -45,6 +45,7 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
|
||||
|
||||
const [DocumentView, setDocumentView] = useState<React.ReactNode>(undefined)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const hasRenderedDocument = useRef(false)
|
||||
|
||||
const getDocumentView = useCallback(
|
||||
(docID?: number | string) => {
|
||||
@@ -142,8 +143,9 @@ export const DocumentDrawerContent: React.FC<DocumentDrawerProps> = ({
|
||||
}, [getDocumentView])
|
||||
|
||||
useEffect(() => {
|
||||
if (!DocumentView) {
|
||||
if (!DocumentView && !hasRenderedDocument.current) {
|
||||
getDocumentView(existingDocID)
|
||||
hasRenderedDocument.current = true
|
||||
}
|
||||
}, [DocumentView, getDocumentView, existingDocID])
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ type RenderDocument = (args: {
|
||||
redirectAfterDelete?: boolean
|
||||
redirectAfterDuplicate?: boolean
|
||||
signal?: AbortSignal
|
||||
}) => Promise<{ docID: string; Document: React.ReactNode }>
|
||||
}) => Promise<{ data: Data; Document: React.ReactNode }>
|
||||
|
||||
type GetDocumentSlots = (args: {
|
||||
collectionSlug: string
|
||||
@@ -129,16 +129,12 @@ export const ServerFunctionsProvider: React.FC<{
|
||||
const { signal: remoteSignal, ...rest } = args || {}
|
||||
|
||||
try {
|
||||
if (!remoteSignal?.aborted) {
|
||||
const result = (await serverFunction({
|
||||
name: 'render-document',
|
||||
args: { fallbackLocale: false, ...rest },
|
||||
})) as { docID: string; Document: React.ReactNode }
|
||||
})) as { data: Data; Document: React.ReactNode }
|
||||
|
||||
if (!remoteSignal?.aborted) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
} catch (_err) {
|
||||
console.error(_err) // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
@@ -179,10 +179,9 @@ describe('fields - relationship', () => {
|
||||
await expect(options).toHaveCount(2) // two docs
|
||||
await options.nth(0).click()
|
||||
await expect(field).toContainText(relationOneDoc.id)
|
||||
await trackNetworkRequests(page, `/api/${relationOneSlug}`, async () => {
|
||||
await saveDocAndAssert(page)
|
||||
await wait(200)
|
||||
await trackNetworkRequests(page, `/api/${relationOneSlug}`, {
|
||||
beforePoll: async () => await page.reload(),
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { expect } from '@playwright/test'
|
||||
export const trackNetworkRequests = async (
|
||||
page: Page,
|
||||
url: string,
|
||||
action: () => Promise<any>,
|
||||
options?: {
|
||||
allowedNumberOfRequests?: number
|
||||
beforePoll?: () => Promise<any> | void
|
||||
@@ -26,6 +27,8 @@ export const trackNetworkRequests = async (
|
||||
}
|
||||
})
|
||||
|
||||
await action()
|
||||
|
||||
if (typeof beforePoll === 'function') {
|
||||
await beforePoll()
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import type { BrowserContext, Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { navigateToDoc } from 'helpers/e2e/navigateToDoc.js'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
@@ -43,6 +44,7 @@ import {
|
||||
throttleTest,
|
||||
} from '../helpers.js'
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { trackNetworkRequests } from '../helpers/e2e/trackNetworkRequests.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { reInitializeDB } from '../helpers/reInitializeDB.js'
|
||||
import { waitForAutoSaveToRunAndComplete } from '../helpers/waitForAutoSaveToRunAndComplete.js'
|
||||
@@ -394,6 +396,42 @@ describe('versions', () => {
|
||||
expect(page.url()).toMatch(/\/versions$/)
|
||||
})
|
||||
|
||||
test('collection - should autosave', async () => {
|
||||
await page.goto(autosaveURL.create)
|
||||
await page.locator('#field-title').fill('autosave title')
|
||||
await waitForAutoSaveToRunAndComplete(page)
|
||||
await expect(page.locator('#field-title')).toHaveValue('autosave title')
|
||||
|
||||
const { id: postID } = await payload.create({
|
||||
collection: postCollectionSlug,
|
||||
data: {
|
||||
title: 'post title',
|
||||
description: 'post description',
|
||||
},
|
||||
})
|
||||
|
||||
await page.goto(postURL.edit(postID))
|
||||
|
||||
await trackNetworkRequests(
|
||||
page,
|
||||
`${serverURL}/admin/collections/${postCollectionSlug}/${postID}`,
|
||||
async () => {
|
||||
await page
|
||||
.locator(
|
||||
'#field-relationToAutosaves.field-type.relationship .relationship-add-new__add-button.doc-drawer__toggler',
|
||||
)
|
||||
.click()
|
||||
},
|
||||
{
|
||||
allowedNumberOfRequests: 1,
|
||||
},
|
||||
)
|
||||
|
||||
const drawer = page.locator('[id^=doc-drawer_autosave-posts_1_]')
|
||||
await expect(drawer).toBeVisible()
|
||||
await expect(drawer.locator('.id-label')).toBeVisible()
|
||||
})
|
||||
|
||||
test('global - should autosave', async () => {
|
||||
const url = new AdminUrlUtil(serverURL, autoSaveGlobalSlug)
|
||||
await page.goto(url.global(autoSaveGlobalSlug))
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": [
|
||||
"./test/_community/config.ts"
|
||||
"./test/versions/config.ts"
|
||||
],
|
||||
"@payloadcms/live-preview": [
|
||||
"./packages/live-preview/src"
|
||||
|
||||
Reference in New Issue
Block a user