fix(ui): infinite loading states when adding blocks or array rows (#10175)

Fixes #10070. Adding new blocks or array rows can randomly get stuck
within an infinite loading state. This was because the abort controllers
responsible for disregarding duplicate `onChange` and `onSave` events
was not properly resetting its refs across invocations. This caused
subsequent event handlers to incorrectly abort themselves, leading to
unresolved requests and a `null` form state. Similarly, the cleanup
effects responsible for aborting these requests on component unmount
were also referencing its `current` property directly off the refs,
which can possible be stale if not first set as a variable outside the
return function.

This PR also carries over some missing `onSave` logic from the default
edit view into the live preview view. In the future the logic between
these two views should be standardized, as they're nearly identical but
often become out of sync. This can likely be done through the use of
reusable hooks, such as `useOnSave`, `useOnChange`, etc. Same with the
document locking functionality which is complex and deeply integrated
into each of these views.
This commit is contained in:
Jacob Fletcher
2024-12-26 12:17:06 -05:00
committed by GitHub
parent 8debb68db2
commit b33f4b0143
15 changed files with 611 additions and 392 deletions

View File

@@ -17,7 +17,7 @@ import { useEditDepth } from '../../../providers/EditDepth/index.js'
import { OperationProvider } from '../../../providers/Operation/index.js'
import { useServerFunctions } from '../../../providers/ServerFunctions/index.js'
import { useUploadEdits } from '../../../providers/UploadEdits/index.js'
import { abortAndIgnore } from '../../../utilities/abortAndIgnore.js'
import { abortAndIgnore, handleAbortRef } from '../../../utilities/abortAndIgnore.js'
import { formatAdminURL } from '../../../utilities/formatAdminURL.js'
import { useDocumentDrawerContext } from '../../DocumentDrawer/Provider.js'
import { DocumentFields } from '../../DocumentFields/index.js'
@@ -56,7 +56,7 @@ export function EditForm({ submitted }: EditFormProps) {
getEntityConfig,
} = useConfig()
const formStateAbortControllerRef = React.useRef<AbortController>(null)
const abortOnChangeRef = React.useRef<AbortController>(null)
const collectionConfig = getEntityConfig({ collectionSlug: docSlug }) as ClientCollectionConfig
const router = useRouter()
@@ -111,12 +111,10 @@ export function EditForm({ submitted }: EditFormProps) {
const onChange: NonNullable<FormProps['onChange']>[0] = useCallback(
async ({ formState: prevFormState }) => {
abortAndIgnore(formStateAbortControllerRef.current)
const controller = new AbortController()
formStateAbortControllerRef.current = controller
const controller = handleAbortRef(abortOnChangeRef)
const docPreferences = await getDocPreferences()
const { state: newFormState } = await getFormState({
collectionSlug,
docPermissions,
@@ -127,14 +125,18 @@ export function EditForm({ submitted }: EditFormProps) {
signal: controller.signal,
})
abortOnChangeRef.current = null
return newFormState
},
[collectionSlug, schemaPath, getDocPreferences, getFormState, docPermissions],
)
useEffect(() => {
const abortOnChange = abortOnChangeRef.current
return () => {
abortAndIgnore(formStateAbortControllerRef.current)
abortAndIgnore(abortOnChange)
}
}, [])