fix(ui): client side doc data not updating after save (#9340)

### What?
When a document is saved the data from useDocumentInfo was stale.

### Why?
Previously we would refresh the entire document by calling the
form-state endpoint, we no longer do that.

### How?
Adds a new variable accessible from useDocumentInfo,
`savedDocumentData`, that is updated when the document is successfully
saved and defaults to initialData.
This commit is contained in:
Jarrod Flesch
2024-11-19 13:22:23 -05:00
committed by GitHub
parent 69b7a11a28
commit 661f450c61
4 changed files with 59 additions and 33 deletions

View File

@@ -1,13 +1,13 @@
'use client' 'use client'
import type { FormState, SanitizedCollectionConfig, UploadEdits } from 'payload' import type { FormState, SanitizedCollectionConfig, UploadEdits } from 'payload'
import { isImage, reduceFieldsToValues } from 'payload/shared' import { isImage } from 'payload/shared'
import React, { useCallback, useEffect, useRef, useState } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { FieldError } from '../../fields/FieldError/index.js' import { FieldError } from '../../fields/FieldError/index.js'
import { fieldBaseClass } from '../../fields/shared/index.js' import { fieldBaseClass } from '../../fields/shared/index.js'
import { useForm } from '../../forms/Form/index.js' import { useForm, useFormProcessing } from '../../forms/Form/index.js'
import { useField } from '../../forms/useField/index.js' import { useField } from '../../forms/useField/index.js'
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js' import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { EditDepthProvider } from '../../providers/EditDepth/index.js' import { EditDepthProvider } from '../../providers/EditDepth/index.js'
@@ -94,15 +94,15 @@ export const Upload: React.FC<UploadProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { setModified } = useForm() const { setModified } = useForm()
const { resetUploadEdits, updateUploadEdits, uploadEdits } = useUploadEdits() const { resetUploadEdits, updateUploadEdits, uploadEdits } = useUploadEdits()
const { docPermissions } = useDocumentInfo() const { docPermissions, savedDocumentData } = useDocumentInfo()
const isFormSubmitting = useFormProcessing()
const { errorMessage, setValue, showError, value } = useField<File>({ const { errorMessage, setValue, showError, value } = useField<File>({
path: 'file', path: 'file',
validate, validate,
}) })
const [doc, setDoc] = useState(reduceFieldsToValues(initialState || {}, true))
const [fileSrc, setFileSrc] = useState<null | string>(null) const [fileSrc, setFileSrc] = useState<null | string>(null)
const [replacingFile, setReplacingFile] = useState(false) const [removedFile, setRemovedFile] = useState(false)
const [filename, setFilename] = useState<string>(value?.name || '') const [filename, setFilename] = useState<string>(value?.name || '')
const [showUrlInput, setShowUrlInput] = useState(false) const [showUrlInput, setShowUrlInput] = useState(false)
const [fileUrl, setFileUrl] = useState<string>('') const [fileUrl, setFileUrl] = useState<string>('')
@@ -156,11 +156,10 @@ export const Upload: React.FC<UploadProps> = (props) => {
) )
const handleFileRemoval = useCallback(() => { const handleFileRemoval = useCallback(() => {
setReplacingFile(true) setRemovedFile(true)
handleFileChange(null) handleFileChange(null)
setFileSrc('') setFileSrc('')
setFileUrl('') setFileUrl('')
setDoc({})
resetUploadEdits() resetUploadEdits()
setShowUrlInput(false) setShowUrlInput(false)
}, [handleFileChange, resetUploadEdits]) }, [handleFileChange, resetUploadEdits])
@@ -192,11 +191,10 @@ export const Upload: React.FC<UploadProps> = (props) => {
} }
useEffect(() => { useEffect(() => {
setDoc(reduceFieldsToValues(initialState || {}, true))
if (initialState?.file?.value instanceof File) { if (initialState?.file?.value instanceof File) {
setFileSrc(URL.createObjectURL(initialState.file.value)) setFileSrc(URL.createObjectURL(initialState.file.value))
setRemovedFile(false)
} }
setReplacingFile(false)
}, [initialState]) }, [initialState])
useEffect(() => { useEffect(() => {
@@ -205,6 +203,12 @@ export const Upload: React.FC<UploadProps> = (props) => {
} }
}, [showUrlInput]) }, [showUrlInput])
useEffect(() => {
if (isFormSubmitting) {
setRemovedFile(false)
}
}, [isFormSubmitting])
const canRemoveUpload = const canRemoveUpload =
docPermissions?.update && 'delete' in docPermissions && docPermissions?.delete docPermissions?.update && 'delete' in docPermissions && docPermissions?.delete
@@ -222,19 +226,19 @@ export const Upload: React.FC<UploadProps> = (props) => {
return ( return (
<div className={[fieldBaseClass, baseClass].filter(Boolean).join(' ')}> <div className={[fieldBaseClass, baseClass].filter(Boolean).join(' ')}>
<FieldError message={errorMessage} showError={showError} /> <FieldError message={errorMessage} showError={showError} />
{doc.filename && !replacingFile && ( {savedDocumentData && savedDocumentData.filename && !removedFile && (
<FileDetails <FileDetails
collectionSlug={collectionSlug} collectionSlug={collectionSlug}
customUploadActions={customActions} customUploadActions={customActions}
doc={doc} doc={savedDocumentData}
enableAdjustments={showCrop || showFocalPoint} enableAdjustments={showCrop || showFocalPoint}
handleRemove={canRemoveUpload ? handleFileRemoval : undefined} handleRemove={canRemoveUpload ? handleFileRemoval : undefined}
hasImageSizes={hasImageSizes} hasImageSizes={hasImageSizes}
imageCacheTag={doc.updatedAt} imageCacheTag={savedDocumentData.updatedAt}
uploadConfig={uploadConfig} uploadConfig={uploadConfig}
/> />
)} )}
{(!doc.filename || replacingFile) && ( {(!savedDocumentData?.filename || removedFile) && (
<div className={`${baseClass}__upload`}> <div className={`${baseClass}__upload`}>
{!value && !showUrlInput && ( {!value && !showUrlInput && (
<Dropzone onChange={handleFileSelection}> <Dropzone onChange={handleFileSelection}>
@@ -339,7 +343,7 @@ export const Upload: React.FC<UploadProps> = (props) => {
<UploadActions <UploadActions
customActions={customActions} customActions={customActions}
enableAdjustments={showCrop || showFocalPoint} enableAdjustments={showCrop || showFocalPoint}
enablePreviewSizes={hasImageSizes && doc.filename && !replacingFile} enablePreviewSizes={hasImageSizes && savedDocumentData?.filename && !removedFile}
mimeType={value.type} mimeType={value.type}
/> />
</div> </div>
@@ -356,17 +360,17 @@ export const Upload: React.FC<UploadProps> = (props) => {
)} )}
</div> </div>
)} )}
{(value || doc.filename) && ( {(value || savedDocumentData?.filename) && (
<EditDepthProvider> <EditDepthProvider>
<Drawer Header={null} slug={editDrawerSlug}> <Drawer Header={null} slug={editDrawerSlug}>
<EditUpload <EditUpload
fileName={value?.name || doc?.filename} fileName={value?.name || savedDocumentData?.filename}
fileSrc={doc?.url || fileSrc} fileSrc={savedDocumentData?.url || fileSrc}
imageCacheTag={doc.updatedAt} imageCacheTag={savedDocumentData?.updatedAt}
initialCrop={uploadEdits?.crop ?? undefined} initialCrop={uploadEdits?.crop ?? undefined}
initialFocalPoint={{ initialFocalPoint={{
x: uploadEdits?.focalPoint?.x || doc.focalX || 50, x: uploadEdits?.focalPoint?.x || savedDocumentData?.focalX || 50,
y: uploadEdits?.focalPoint?.y || doc.focalY || 50, y: uploadEdits?.focalPoint?.y || savedDocumentData?.focalY || 50,
}} }}
onSave={onEditsSave} onSave={onEditsSave}
showCrop={showCrop} showCrop={showCrop}
@@ -375,14 +379,18 @@ export const Upload: React.FC<UploadProps> = (props) => {
</Drawer> </Drawer>
</EditDepthProvider> </EditDepthProvider>
)} )}
{doc && hasImageSizes && ( {savedDocumentData && hasImageSizes && (
<Drawer <Drawer
className={`${baseClass}__previewDrawer`} className={`${baseClass}__previewDrawer`}
hoverTitle hoverTitle
slug={sizePreviewSlug} slug={sizePreviewSlug}
title={t('upload:sizesFor', { label: doc?.filename })} title={t('upload:sizesFor', { label: savedDocumentData.filename })}
> >
<PreviewSizes doc={doc} imageCacheTag={doc.updatedAt} uploadConfig={uploadConfig} /> <PreviewSizes
doc={savedDocumentData}
imageCacheTag={savedDocumentData.updatedAt}
uploadConfig={uploadConfig}
/>
</Drawer> </Drawer>
)} )}
</div> </div>

View File

@@ -42,7 +42,7 @@ const DocumentInfo: React.FC<
hasPublishedDoc: hasPublishedDocFromProps, hasPublishedDoc: hasPublishedDocFromProps,
hasPublishPermission: hasPublishPermissionFromProps, hasPublishPermission: hasPublishPermissionFromProps,
hasSavePermission: hasSavePermissionFromProps, hasSavePermission: hasSavePermissionFromProps,
initialData: data, initialData,
initialState, initialState,
isLocked: isLockedFromProps, isLocked: isLockedFromProps,
lastUpdateTime: lastUpdateTimeFromProps, lastUpdateTime: lastUpdateTimeFromProps,
@@ -84,7 +84,7 @@ const DocumentInfo: React.FC<
const [documentTitle, setDocumentTitle] = useState(() => const [documentTitle, setDocumentTitle] = useState(() =>
formatDocTitle({ formatDocTitle({
collectionConfig, collectionConfig,
data: { ...data, id }, data: { ...(initialData || {}), id },
dateFormat, dateFormat,
fallback: id?.toString(), fallback: id?.toString(),
globalConfig, globalConfig,
@@ -105,8 +105,9 @@ const DocumentInfo: React.FC<
const [documentIsLocked, setDocumentIsLocked] = useState<boolean | undefined>(isLockedFromProps) const [documentIsLocked, setDocumentIsLocked] = useState<boolean | undefined>(isLockedFromProps)
const [currentEditor, setCurrentEditor] = useState<ClientUser | null>(currentEditorFromProps) const [currentEditor, setCurrentEditor] = useState<ClientUser | null>(currentEditorFromProps)
const [lastUpdateTime, setLastUpdateTime] = useState<number>(lastUpdateTimeFromProps) const [lastUpdateTime, setLastUpdateTime] = useState<number>(lastUpdateTimeFromProps)
const [savedDocumentData, setSavedDocumentData] = useState(initialData)
const isInitializing = initialState === undefined || data === undefined const isInitializing = initialState === undefined || initialData === undefined
const { getPreference, setPreference } = usePreferences() const { getPreference, setPreference } = usePreferences()
const { code: locale } = useLocale() const { code: locale } = useLocale()
@@ -251,18 +252,25 @@ const DocumentInfo: React.FC<
} }
}, [collectionConfig, globalConfig, versionCount]) }, [collectionConfig, globalConfig, versionCount])
const updateSavedDocumentData = React.useCallback<DocumentInfoContext['updateSavedDocumentData']>(
(json) => {
setSavedDocumentData(json)
},
[],
)
useEffect(() => { useEffect(() => {
setDocumentTitle( setDocumentTitle(
formatDocTitle({ formatDocTitle({
collectionConfig, collectionConfig,
data: { ...data, id }, data: { ...savedDocumentData, id },
dateFormat, dateFormat,
fallback: id?.toString(), fallback: id?.toString(),
globalConfig, globalConfig,
i18n, i18n,
}), }),
) )
}, [collectionConfig, globalConfig, data, dateFormat, i18n, id]) }, [collectionConfig, globalConfig, savedDocumentData, dateFormat, i18n, id])
// clean on unmount // clean on unmount
useEffect(() => { useEffect(() => {
@@ -306,12 +314,13 @@ const DocumentInfo: React.FC<
hasPublishPermission, hasPublishPermission,
hasSavePermission, hasSavePermission,
incrementVersionCount, incrementVersionCount,
initialData: data, initialData,
initialState, initialState,
isInitializing, isInitializing,
lastUpdateTime, lastUpdateTime,
mostRecentVersionIsAutosaved, mostRecentVersionIsAutosaved,
preferencesKey, preferencesKey,
savedDocumentData,
setCurrentEditor, setCurrentEditor,
setDocFieldPreferences, setDocFieldPreferences,
setDocumentIsLocked, setDocumentIsLocked,
@@ -324,6 +333,7 @@ const DocumentInfo: React.FC<
unlockDocument, unlockDocument,
unpublishedVersionCount, unpublishedVersionCount,
updateDocumentEditor, updateDocumentEditor,
updateSavedDocumentData,
versionCount, versionCount,
} }

View File

@@ -57,6 +57,7 @@ export type DocumentInfoContext = {
lastUpdateTime?: number lastUpdateTime?: number
mostRecentVersionIsAutosaved: boolean mostRecentVersionIsAutosaved: boolean
preferencesKey?: string preferencesKey?: string
savedDocumentData?: Data
setCurrentEditor?: React.Dispatch<React.SetStateAction<ClientUser>> setCurrentEditor?: React.Dispatch<React.SetStateAction<ClientUser>>
setDocFieldPreferences: ( setDocFieldPreferences: (
field: string, field: string,
@@ -72,5 +73,6 @@ export type DocumentInfoContext = {
unlockDocument: (docId: number | string, slug: string) => Promise<void> unlockDocument: (docId: number | string, slug: string) => Promise<void>
unpublishedVersionCount: number unpublishedVersionCount: number
updateDocumentEditor: (docId: number | string, slug: string, user: ClientUser) => Promise<void> updateDocumentEditor: (docId: number | string, slug: string, user: ClientUser) => Promise<void>
updateSavedDocumentData: (data: Data) => void
versionCount: number versionCount: number
} & DocumentInfoProps } & DocumentInfoProps

View File

@@ -71,17 +71,18 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
hasPublishPermission, hasPublishPermission,
hasSavePermission, hasSavePermission,
incrementVersionCount, incrementVersionCount,
initialData: data,
initialState, initialState,
isEditing, isEditing,
isInitializing, isInitializing,
lastUpdateTime, lastUpdateTime,
redirectAfterDelete, redirectAfterDelete,
redirectAfterDuplicate, redirectAfterDuplicate,
savedDocumentData,
setCurrentEditor, setCurrentEditor,
setDocumentIsLocked, setDocumentIsLocked,
unlockDocument, unlockDocument,
updateDocumentEditor, updateDocumentEditor,
updateSavedDocumentData,
} = useDocumentInfo() } = useDocumentInfo()
const { const {
@@ -212,6 +213,10 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
incrementVersionCount() incrementVersionCount()
if (typeof updateSavedDocumentData === 'function') {
void updateSavedDocumentData(json?.doc || {})
}
if (typeof onSaveFromContext === 'function') { if (typeof onSaveFromContext === 'function') {
void onSaveFromContext({ void onSaveFromContext({
...json, ...json,
@@ -238,6 +243,7 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
await getDocPermissions(json) await getDocPermissions(json)
}, },
[ [
updateSavedDocumentData,
reportUpdate, reportUpdate,
id, id,
entitySlug, entitySlug,
@@ -479,7 +485,7 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
SaveButton, SaveButton,
SaveDraftButton, SaveDraftButton,
}} }}
data={data} data={savedDocumentData}
disableActions={disableActions} disableActions={disableActions}
disableCreate={disableCreate} disableCreate={disableCreate}
hasPublishPermission={hasPublishPermission} hasPublishPermission={hasPublishPermission}
@@ -521,7 +527,7 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
className={`${baseClass}__auth`} className={`${baseClass}__auth`}
collectionSlug={collectionConfig.slug} collectionSlug={collectionConfig.slug}
disableLocalStrategy={collectionConfig.auth?.disableLocalStrategy} disableLocalStrategy={collectionConfig.auth?.disableLocalStrategy}
email={data?.email} email={savedDocumentData?.email}
loginWithUsername={auth?.loginWithUsername} loginWithUsername={auth?.loginWithUsername}
operation={operation} operation={operation}
readOnly={!hasSavePermission} readOnly={!hasSavePermission}
@@ -529,7 +535,7 @@ export const DefaultEditView: React.FC<ClientSideEditViewProps> = ({
setSchemaPathSegments={setSchemaPathSegments} setSchemaPathSegments={setSchemaPathSegments}
setValidateBeforeSubmit={setValidateBeforeSubmit} setValidateBeforeSubmit={setValidateBeforeSubmit}
useAPIKey={auth.useAPIKey} useAPIKey={auth.useAPIKey}
username={data?.username} username={savedDocumentData?.username}
verify={auth.verify} verify={auth.verify}
/> />
)} )}