Skip to content

Commit

Permalink
fix: ensures autosave only runs sequentially (#9892)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jmikrut authored Dec 11, 2024
1 parent 5223990 commit a0f0316
Showing 1 changed file with 27 additions and 4 deletions.
31 changes: 27 additions & 4 deletions packages/ui/src/elements/Autosave/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { ClientCollectionConfig, ClientGlobalConfig } from 'payload'

import { versionDefaults } from 'payload/shared'
import React, { useEffect, useRef, useState } from 'react'
import React, { useRef, useState } from 'react'
import { toast } from 'sonner'

import {
Expand Down Expand Up @@ -49,6 +49,9 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
setLastUpdateTime,
setMostRecentVersionIsAutosaved,
} = useDocumentInfo()
const queueRef = useRef([])
const isProcessingRef = useRef(false)

const { reportUpdate } = useDocumentEvents()
const { dispatchFields, setSubmitted } = useForm()
const submitted = useFormSubmitted()
Expand Down Expand Up @@ -88,6 +91,25 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
// can always retrieve the most to date locale
localeRef.current = locale

const processQueue = React.useCallback(async () => {
if (isProcessingRef.current || queueRef.current.length === 0) {
return
}

isProcessingRef.current = true
const latestAction = queueRef.current[queueRef.current.length - 1]
queueRef.current = []

try {
await latestAction()
} finally {
isProcessingRef.current = false
if (queueRef.current.length > 0) {
await processQueue()
}
}
}, [])

// When debounced fields change, autosave
useIgnoredEffect(
() => {
Expand All @@ -97,7 +119,7 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
let startTimestamp = undefined
let endTimestamp = undefined

const autosave = () => {
const autosave = async () => {
if (modified) {
startTimestamp = new Date().getTime()

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

if (!skipSubmission) {
void fetch(url, {
await fetch(url, {
body: JSON.stringify(data),
credentials: 'include',
headers: {
Expand Down Expand Up @@ -229,7 +251,8 @@ export const Autosave: React.FC<Props> = ({ id, collection, global: globalDoc })
}
}

void autosave()
queueRef.current.push(autosave)
void processQueue()

return () => {
if (autosaveTimeout) {
Expand Down

0 comments on commit a0f0316

Please sign in to comment.