fix(ui): field bulk upload showing stale data (#13006)

This commit is contained in:
Jarrod Flesch
2025-07-02 10:11:51 -04:00
committed by GitHub
parent 50029532aa
commit 9ba740e472
10 changed files with 291 additions and 395 deletions

View File

@@ -1,7 +1,5 @@
'use client'
import { useRouter, useSearchParams } from 'next/navigation.js'
import { formatAdminURL } from 'payload/shared'
import React, { useCallback, useEffect } from 'react'
import type { EditFormProps } from './types.js'
@@ -12,9 +10,7 @@ import { WatchChildErrors } from '../../../forms/WatchChildErrors/index.js'
import { useConfig } from '../../../providers/Config/index.js'
import { useDocumentEvents } from '../../../providers/DocumentEvents/index.js'
import { useDocumentInfo } from '../../../providers/DocumentInfo/index.js'
import { useEditDepth } from '../../../providers/EditDepth/index.js'
import { OperationProvider } from '../../../providers/Operation/index.js'
import { useRouteTransition } from '../../../providers/RouteTransition/index.js'
import { useServerFunctions } from '../../../providers/ServerFunctions/index.js'
import { abortAndIgnore, handleAbortRef } from '../../../utilities/abortAndIgnore.js'
import { useDocumentDrawerContext } from '../../DocumentDrawer/Provider.js'
@@ -23,7 +19,6 @@ import { MoveDocToFolder } from '../../FolderView/MoveDocToFolder/index.js'
import { Upload_v4 } from '../../Upload/index.js'
import { useFormsManager } from '../FormsManager/index.js'
import './index.scss'
import { BulkUploadProvider } from '../index.js'
const baseClass = 'collection-edit'
@@ -44,7 +39,6 @@ export function EditForm({
getDocPreferences,
hasSavePermission,
initialState,
isEditing,
isInitializing,
Upload: CustomUpload,
} = useDocumentInfo()
@@ -54,23 +48,14 @@ export function EditForm({
const { getFormState } = useServerFunctions()
const {
config: {
folders,
routes: { admin: adminRoute },
},
config: { folders },
getEntityConfig,
} = useConfig()
const abortOnChangeRef = React.useRef<AbortController>(null)
const collectionConfig = getEntityConfig({ collectionSlug: docSlug })
const router = useRouter()
const depth = useEditDepth()
const params = useSearchParams()
const { reportUpdate } = useDocumentEvents()
const { startRouteTransition } = useRouteTransition()
const locale = params.get('locale')
const collectionSlug = collectionConfig.slug
@@ -89,31 +74,9 @@ export function EditForm({
operation: 'create',
})
}
if (!isEditing && depth < 2) {
// Redirect to the same locale if it's been set
const redirectRoute = formatAdminURL({
adminRoute,
path: `/collections/${collectionSlug}/${json?.doc?.id}${locale ? `?locale=${locale}` : ''}`,
})
startRouteTransition(() => router.push(redirectRoute))
} else {
resetUploadEdits()
}
resetUploadEdits()
},
[
adminRoute,
collectionSlug,
depth,
isEditing,
locale,
onSaveFromContext,
reportUpdate,
resetUploadEdits,
router,
startRouteTransition,
],
[collectionSlug, onSaveFromContext, reportUpdate, resetUploadEdits],
)
const onChange: NonNullable<FormProps['onChange']>[0] = useCallback(
@@ -150,54 +113,52 @@ export function EditForm({
return (
<OperationProvider operation="create">
<BulkUploadProvider>
<Form
action={action}
className={`${baseClass}__form`}
disabled={isInitializing || !hasSavePermission}
initialState={isInitializing ? undefined : initialState}
isInitializing={isInitializing}
method="POST"
onChange={[onChange]}
onSuccess={onSave}
submitted={submitted}
>
<DocumentFields
BeforeFields={
<React.Fragment>
{CustomUpload || (
<Upload_v4
collectionSlug={collectionConfig.slug}
customActions={[
folders && collectionConfig.folders && (
<MoveDocToFolder
buttonProps={{
buttonStyle: 'pill',
size: 'small',
}}
folderCollectionSlug={folders.slug}
folderFieldName={folders.fieldName}
key="move-doc-to-folder"
/>
),
].filter(Boolean)}
initialState={initialState}
resetUploadEdits={resetUploadEdits}
updateUploadEdits={updateUploadEdits}
uploadConfig={collectionConfig.upload}
uploadEdits={uploadEdits}
/>
)}
</React.Fragment>
}
docPermissions={docPermissions}
fields={collectionConfig.fields}
schemaPathSegments={[collectionConfig.slug]}
/>
<ReportAllErrors />
<GetFieldProxy />
</Form>
</BulkUploadProvider>
<Form
action={action}
className={`${baseClass}__form`}
disabled={isInitializing || !hasSavePermission}
initialState={isInitializing ? undefined : initialState}
isInitializing={isInitializing}
method="POST"
onChange={[onChange]}
onSuccess={onSave}
submitted={submitted}
>
<DocumentFields
BeforeFields={
<React.Fragment>
{CustomUpload || (
<Upload_v4
collectionSlug={collectionConfig.slug}
customActions={[
folders && collectionConfig.folders && (
<MoveDocToFolder
buttonProps={{
buttonStyle: 'pill',
size: 'small',
}}
folderCollectionSlug={folders.slug}
folderFieldName={folders.fieldName}
key="move-doc-to-folder"
/>
),
].filter(Boolean)}
initialState={initialState}
resetUploadEdits={resetUploadEdits}
updateUploadEdits={updateUploadEdits}
uploadConfig={collectionConfig.upload}
uploadEdits={uploadEdits}
/>
)}
</React.Fragment>
}
docPermissions={docPermissions}
fields={collectionConfig.fields}
schemaPathSegments={[collectionConfig.slug]}
/>
<ReportAllErrors />
<GetFieldProxy />
</Form>
</OperationProvider>
)
}

View File

@@ -11,9 +11,11 @@ import type { FormProps } from '../../../forms/Form/index.js'
import type { OnFieldSelect } from '../../FieldSelect/index.js'
import type { FieldOption } from '../../FieldSelect/reduceFieldOptions.js'
import type { State } from '../FormsManager/reducer.js'
import type { EditManyBulkUploadsProps } from './index.js'
import { Button } from '../../../elements/Button/index.js'
import { Form } from '../../../forms/Form/index.js'
import { FieldPathContext } from '../../../forms/RenderFields/context.js'
import { RenderField } from '../../../forms/RenderFields/RenderField.js'
import { XIcon } from '../../../icons/X/index.js'
import { useAuth } from '../../../providers/Auth/index.js'
@@ -22,7 +24,7 @@ import { useTranslation } from '../../../providers/Translation/index.js'
import { abortAndIgnore, handleAbortRef } from '../../../utilities/abortAndIgnore.js'
import { FieldSelect } from '../../FieldSelect/index.js'
import { useFormsManager } from '../FormsManager/index.js'
import { baseClass, type EditManyBulkUploadsProps } from './index.js'
import { baseClass } from './index.js'
import './index.scss'
import '../../../forms/RenderFields/index.scss'
@@ -169,23 +171,25 @@ export const EditManyBulkUploadsDrawerContent: React.FC<
/>
{selectedFields.length === 0 ? null : (
<div className="render-fields">
{selectedFields.map((option, i) => {
const {
value: { field, fieldPermissions, path },
} = option
<FieldPathContext value={undefined}>
{selectedFields.map((option, i) => {
const {
value: { field, fieldPermissions, path },
} = option
return (
<RenderField
clientFieldConfig={field}
indexPath=""
key={`${path}-${i}`}
parentPath=""
parentSchemaPath=""
path={path}
permissions={fieldPermissions}
/>
)
})}
return (
<RenderField
clientFieldConfig={field}
indexPath=""
key={`${path}-${i}`}
parentPath=""
parentSchemaPath=""
path={path}
permissions={fieldPermissions}
/>
)
})}
</FieldPathContext>
</div>
)}
<div className={`${baseClass}__sidebar-wrap`}>

View File

@@ -118,7 +118,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
const { toggleLoadingOverlay } = useLoadingOverlay()
const { closeModal } = useModal()
const { collectionSlug, drawerSlug, initialFiles, onSuccess } = useBulkUpload()
const { collectionSlug, drawerSlug, initialFiles, onSuccess, setInitialFiles } = useBulkUpload()
const [isUploading, setIsUploading] = React.useState(false)
const [loadingText, setLoadingText] = React.useState('')
@@ -366,13 +366,10 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
setIsUploading(false)
const remainingForms = []
const thumbnailIndexesToRemove = []
currentForms.forEach(({ errorCount }, i) => {
if (errorCount) {
remainingForms.push(currentForms[i])
} else {
thumbnailIndexesToRemove.push(i)
}
})
@@ -401,8 +398,13 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
totalErrorCount: remainingForms.reduce((acc, { errorCount }) => acc + errorCount, 0),
},
})
if (remainingForms.length === 0) {
setInitialFiles(undefined)
}
},
[
setInitialFiles,
actionURL,
collectionSlug,
getUploadHandler,

View File

@@ -89,61 +89,57 @@ export function BulkUploadDrawer() {
type BulkUploadContext = {
collectionSlug: string
currentActivePath: string
drawerSlug: string
initialFiles: FileList
maxFiles: number
onCancel: () => void
onSuccess: (newDocs: JsonObject[], errorCount: number) => void
setCollectionSlug: (slug: string) => void
setCurrentActivePath: (path: string) => void
setInitialFiles: (files: FileList) => void
setMaxFiles: (maxFiles: number) => void
setOnCancel: (onCancel: BulkUploadContext['onCancel']) => void
setOnSuccess: (path: string, onSuccess: BulkUploadContext['onSuccess']) => void
setOnSuccess: (onSuccess: BulkUploadContext['onSuccess']) => void
}
const Context = React.createContext<BulkUploadContext>({
collectionSlug: '',
currentActivePath: undefined,
drawerSlug: '',
initialFiles: undefined,
maxFiles: undefined,
onCancel: () => null,
onSuccess: () => null,
setCollectionSlug: () => null,
setCurrentActivePath: () => null,
setInitialFiles: () => null,
setMaxFiles: () => null,
setOnCancel: () => null,
setOnSuccess: () => null,
})
export function BulkUploadProvider({ children }: { readonly children: React.ReactNode }) {
export function BulkUploadProvider({
children,
drawerSlugPrefix,
}: {
readonly children: React.ReactNode
readonly drawerSlugPrefix?: string
}) {
const [collection, setCollection] = React.useState<string>()
const [onSuccessFunctionMap, setOnSuccessFunctionMap] =
React.useState<Record<string, BulkUploadContext['onSuccess']>>()
const [onSuccessFunction, setOnSuccessFunction] = React.useState<BulkUploadContext['onSuccess']>()
const [onCancelFunction, setOnCancelFunction] = React.useState<BulkUploadContext['onCancel']>()
const [initialFiles, setInitialFiles] = React.useState<FileList>(undefined)
const [maxFiles, setMaxFiles] = React.useState<number>(undefined)
const [currentActivePath, setCurrentActivePath] = React.useState<string>(undefined)
const drawerSlug = useBulkUploadDrawerSlug()
const drawerSlug = `${drawerSlugPrefix ? `${drawerSlugPrefix}-` : ''}${useBulkUploadDrawerSlug()}`
const setCollectionSlug: BulkUploadContext['setCollectionSlug'] = (slug) => {
setCollection(slug)
}
const setOnSuccess: BulkUploadContext['setOnSuccess'] = React.useCallback((path, onSuccess) => {
setOnSuccessFunctionMap((prev) => ({
...prev,
[path]: onSuccess,
}))
}, [])
const setOnSuccess: BulkUploadContext['setOnSuccess'] = (onSuccess) => {
setOnSuccessFunction(() => onSuccess)
}
return (
<Context
value={{
collectionSlug: collection,
currentActivePath,
drawerSlug,
initialFiles,
maxFiles,
@@ -153,13 +149,11 @@ export function BulkUploadProvider({ children }: { readonly children: React.Reac
}
},
onSuccess: (docIDs, errorCount) => {
if (onSuccessFunctionMap && Object.hasOwn(onSuccessFunctionMap, currentActivePath)) {
const onSuccessFunction = onSuccessFunctionMap[currentActivePath]
if (typeof onSuccessFunction === 'function') {
onSuccessFunction(docIDs, errorCount)
}
},
setCollectionSlug,
setCurrentActivePath,
setInitialFiles,
setMaxFiles,
setOnCancel: setOnCancelFunction,