fix(ui): autosave infinite loop within document drawer (#13007)

Required for #13005.

Opening an autosave-enabled document within a drawer triggers an
infinite loop when the root document is also autosave-enabled.

This was for two reasons:

1. Autosave would run and change the `updatedAt` timestamp. This would
trigger another run of autosave, and so on. The timestamp is now removed
before comparison to ensure that sequential autosave runs are skipped.

2. The `dequal()` call was not being given the `.current` property off
the ref object. This meant that is was never evaluate to `true` and
therefore never skip unnecessary autosaves to begin with.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210697235723932
This commit is contained in:
Jacob Fletcher
2025-07-02 15:11:38 -04:00
committed by GitHub
parent 335af1b8c9
commit b40c581a27
8 changed files with 59 additions and 55 deletions

View File

@@ -1,12 +1,12 @@
import type { CollectionConfig } from 'payload'
import { autosaveWithValidateCollectionSlug } from '../slugs.js'
import { autosaveWithDraftValidateSlug } from '../slugs.js'
const AutosaveWithValidatePosts: CollectionConfig = {
slug: autosaveWithValidateCollectionSlug,
const AutosaveWithDraftValidate: CollectionConfig = {
slug: autosaveWithDraftValidateSlug,
labels: {
singular: 'Autosave with Validate Post',
plural: 'Autosave with Validate Posts',
singular: 'Autosave with Draft Validate',
plural: 'Autosave with Draft Validate',
},
admin: {
useAsTitle: 'title',
@@ -30,4 +30,4 @@ const AutosaveWithValidatePosts: CollectionConfig = {
],
}
export default AutosaveWithValidatePosts
export default AutosaveWithDraftValidate

View File

@@ -5,7 +5,7 @@ const dirname = path.dirname(filename)
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import AutosavePosts from './collections/Autosave.js'
import AutosaveWithDraftButtonPosts from './collections/AutosaveWithDraftButton.js'
import AutosaveWithValidate from './collections/AutosaveWithValidate.js'
import AutosaveWithDraftValidate from './collections/AutosaveWithDraftValidate.js'
import CustomIDs from './collections/CustomIDs.js'
import { Diff } from './collections/Diff/index.js'
import DisablePublish from './collections/DisablePublish.js'
@@ -41,7 +41,7 @@ export default buildConfigWithDefaults({
Posts,
AutosavePosts,
AutosaveWithDraftButtonPosts,
AutosaveWithValidate,
AutosaveWithDraftValidate,
DraftPosts,
DraftWithMax,
DraftsWithValidate,

View File

@@ -38,6 +38,7 @@ import {
exactText,
initPageConsoleErrorCatch,
saveDocAndAssert,
// throttleTest,
} from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { assertNetworkRequests } from '../helpers/e2e/assertNetworkRequests.js'
@@ -50,7 +51,7 @@ import {
autoSaveGlobalSlug,
autosaveWithDraftButtonGlobal,
autosaveWithDraftButtonSlug,
autosaveWithValidateCollectionSlug,
autosaveWithDraftValidateSlug,
customIDSlug,
diffCollectionSlug,
disablePublishGlobalSlug,
@@ -82,7 +83,7 @@ describe('Versions', () => {
let serverURL: string
let autosaveURL: AdminUrlUtil
let autosaveWithDraftButtonURL: AdminUrlUtil
let autosaveWithValidateURL: AdminUrlUtil
let autosaveWithDraftValidateURL: AdminUrlUtil
let draftWithValidateURL: AdminUrlUtil
let disablePublishURL: AdminUrlUtil
let customIDURL: AdminUrlUtil
@@ -103,11 +104,12 @@ describe('Versions', () => {
})
beforeEach(async () => {
/* await throttleTest({
page,
context,
delay: 'Slow 4G',
}) */
// await throttleTest({
// page,
// context,
// delay: 'Fast 4G',
// })
await reInitializeDB({
serverURL,
snapshotKey: 'versionsTest',
@@ -121,7 +123,7 @@ describe('Versions', () => {
url = new AdminUrlUtil(serverURL, draftCollectionSlug)
autosaveURL = new AdminUrlUtil(serverURL, autosaveCollectionSlug)
autosaveWithDraftButtonURL = new AdminUrlUtil(serverURL, autosaveWithDraftButtonSlug)
autosaveWithValidateURL = new AdminUrlUtil(serverURL, autosaveWithValidateCollectionSlug)
autosaveWithDraftValidateURL = new AdminUrlUtil(serverURL, autosaveWithDraftValidateSlug)
disablePublishURL = new AdminUrlUtil(serverURL, disablePublishSlug)
customIDURL = new AdminUrlUtil(serverURL, customIDSlug)
postURL = new AdminUrlUtil(serverURL, postCollectionSlug)
@@ -1059,7 +1061,7 @@ describe('Versions', () => {
describe('Collections with draft validation', () => {
beforeAll(() => {
autosaveWithValidateURL = new AdminUrlUtil(serverURL, autosaveWithValidateCollectionSlug)
autosaveWithDraftValidateURL = new AdminUrlUtil(serverURL, autosaveWithDraftValidateSlug)
draftWithValidateURL = new AdminUrlUtil(serverURL, draftWithValidateCollectionSlug)
})
@@ -1173,7 +1175,7 @@ describe('Versions', () => {
})
test('- with autosave - can save', async () => {
await page.goto(autosaveWithValidateURL.create)
await page.goto(autosaveWithDraftValidateURL.create)
const titleField = page.locator('#field-title')
await titleField.fill('Initial')
@@ -1191,7 +1193,7 @@ describe('Versions', () => {
test('- with autosave - can safely trigger validation errors and then continue editing', async () => {
// This test has to make sure we don't enter an infinite loop when draft.validate is on and we have autosave enabled
await page.goto(autosaveWithValidateURL.create)
await page.goto(autosaveWithDraftValidateURL.create)
const titleField = page.locator('#field-title')
await titleField.fill('Initial')
@@ -1213,7 +1215,7 @@ describe('Versions', () => {
})
test('- with autosave - shows a prevent leave alert when form is submitted but invalid', async () => {
await page.goto(autosaveWithValidateURL.create)
await page.goto(autosaveWithDraftValidateURL.create)
// Flag to check against if window alert has been displayed and dismissed since we can only check via events
let alertDisplayed = false

View File

@@ -536,6 +536,13 @@ export interface User {
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
}
/**
@@ -1049,6 +1056,13 @@ export interface UsersSelect<T extends boolean = true> {
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema

View File

@@ -8,7 +8,7 @@ import { devUser } from '../credentials.js'
import { executePromises } from '../helpers/executePromises.js'
import { generateLexicalData } from './collections/Diff/generateLexicalData.js'
import {
autosaveWithValidateCollectionSlug,
autosaveWithDraftValidateSlug,
diffCollectionSlug,
draftCollectionSlug,
media2CollectionSlug,
@@ -141,7 +141,7 @@ export async function seed(_payload: Payload, parallel: boolean = false) {
})
await _payload.create({
collection: autosaveWithValidateCollectionSlug,
collection: autosaveWithDraftValidateSlug,
data: {
title: 'Initial seeded title',
},

View File

@@ -2,7 +2,7 @@ export const autosaveCollectionSlug = 'autosave-posts'
export const autosaveWithDraftButtonSlug = 'autosave-with-draft-button-posts'
export const autosaveWithValidateCollectionSlug = 'autosave-with-validate-posts'
export const autosaveWithDraftValidateSlug = 'autosave-with-validate-posts'
export const customIDSlug = 'custom-ids'