Skip to content

Commit a0f0316

Browse files
authored
fix: ensures autosave only runs sequentially (#9892)
Previously, Autosave could trigger 2 parallel fetches where the second could outpace the first, leading to inconsistent results. Now, we use a simple queue-based system where we can push multiple autosave events into a queue, and only the latest autosave will be performed. This also prevents multiple autosaves from ever running in parallel.
1 parent 5223990 commit a0f0316

File tree

1 file changed

+27
-4
lines changed

1 file changed

+27
-4
lines changed

packages/ui/src/elements/Autosave/index.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload'
44

55
import { versionDefaults } from 'payload/shared'
6-
import React, { useEffect, useRef, useState } from 'react'
6+
import React, { useRef, useState } from 'react'
77
import { toast } from 'sonner'
88

99
import {
@@ -49,6 +49,9 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
4949
setLastUpdateTime,
5050
setMostRecentVersionIsAutosaved,
5151
} = useDocumentInfo()
52+
const queueRef = useRef([])
53+
const isProcessingRef = useRef(false)
54+
5255
const { reportUpdate } = useDocumentEvents()
5356
const { dispatchFields, setSubmitted } = useForm()
5457
const submitted = useFormSubmitted()
@@ -88,6 +91,25 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
8891
// can always retrieve the most to date locale
8992
localeRef.current = locale
9093

94+
const processQueue = React.useCallback(async () => {
95+
if (isProcessingRef.current || queueRef.current.length === 0) {
96+
return
97+
}
98+
99+
isProcessingRef.current = true
100+
const latestAction = queueRef.current[queueRef.current.length - 1]
101+
queueRef.current = []
102+
103+
try {
104+
await latestAction()
105+
} finally {
106+
isProcessingRef.current = false
107+
if (queueRef.current.length > 0) {
108+
await processQueue()
109+
}
110+
}
111+
}, [])
112+
91113
// When debounced fields change, autosave
92114
useIgnoredEffect(
93115
() => {
@@ -97,7 +119,7 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
97119
let startTimestamp = undefined
98120
let endTimestamp = undefined
99121

100-
const autosave = () => {
122+
const autosave = async () => {
101123
if (modified) {
102124
startTimestamp = new Date().getTime()
103125

@@ -129,7 +151,7 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
129151
submitted && !valid && versionsConfig?.drafts && versionsConfig?.drafts?.validate
130152

131153
if (!skipSubmission) {
132-
void fetch(url, {
154+
await fetch(url, {
133155
body: JSON.stringify(data),
134156
credentials: 'include',
135157
headers: {
@@ -229,7 +251,8 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
229251
}
230252
}
231253

232-
void autosave()
254+
queueRef.current.push(autosave)
255+
void processQueue()
233256

234257
return () => {
235258
if (autosaveTimeout) {

0 commit comments

Comments
 (0)