fix: move form data retrieval logic to client (#5411)
* fix: only execute onChange if form modified * fix: move document loading logic from RSC to DocumentInfoProvider * fix: make it work for globals * chore: remove unnecessary diffs --------- Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
This commit is contained in:
@@ -67,10 +67,11 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
let DefaultView: EditViewComponent
|
||||
let ErrorView: AdminViewComponent = NotFoundView
|
||||
|
||||
/**
|
||||
let data: DocumentType
|
||||
let docPermissions: DocumentPermissions
|
||||
let preferencesKey: string
|
||||
let fields: Field[]
|
||||
let fields: Field[] **/
|
||||
let docPermissions: DocumentPermissions
|
||||
let hasSavePermission: boolean
|
||||
let apiURL: string
|
||||
let action: string
|
||||
@@ -88,7 +89,8 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
return <NotFoundClient />
|
||||
}
|
||||
|
||||
fields = collectionConfig.fields
|
||||
/**
|
||||
fields = collectionConfig.fields **/
|
||||
action = `${serverURL}${apiRoute}/${collectionSlug}${isEditing ? `/${id}` : ''}`
|
||||
|
||||
hasSavePermission =
|
||||
@@ -120,6 +122,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
return <ErrorView initPageResult={initPageResult} searchParams={searchParams} />
|
||||
}
|
||||
|
||||
/**
|
||||
if (id) {
|
||||
try {
|
||||
data = await payload.findByID({
|
||||
@@ -140,13 +143,15 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
|
||||
preferencesKey = `collection-${collectionSlug}-${id}`
|
||||
}
|
||||
**/
|
||||
}
|
||||
|
||||
if (globalConfig) {
|
||||
docPermissions = permissions?.globals?.[globalSlug]
|
||||
fields = globalConfig.fields
|
||||
hasSavePermission = isEditing && docPermissions?.update?.permission
|
||||
action = `${serverURL}${apiRoute}/globals/${globalSlug}`
|
||||
/**
|
||||
fields = globalConfig.fields **/
|
||||
|
||||
apiURL = `${serverURL}${apiRoute}/${globalSlug}?locale=${locale.code}${
|
||||
globalConfig.versions?.drafts ? '&draft=true' : ''
|
||||
@@ -172,6 +177,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
return <ErrorView initPageResult={initPageResult} searchParams={searchParams} />
|
||||
}
|
||||
|
||||
/**
|
||||
try {
|
||||
data = await payload.findGlobal({
|
||||
slug: globalSlug,
|
||||
@@ -188,29 +194,31 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
return <NotFoundClient />
|
||||
}
|
||||
|
||||
preferencesKey = `global-${globalSlug}`
|
||||
preferencesKey = `global-${globalSlug}` **/
|
||||
}
|
||||
}
|
||||
|
||||
const { docs: [{ value: docPreferences } = { value: null }] = [] } = (await payload.find({
|
||||
collection: 'payload-preferences',
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
where: {
|
||||
key: {
|
||||
equals: preferencesKey,
|
||||
},
|
||||
},
|
||||
})) as any as { docs: { value: DocumentPreferences }[] } // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
/**
|
||||
const { docs: [{ value: docPreferences } = { value: null }] = [] } = (await payload.find({
|
||||
collection: 'payload-preferences',
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
where: {
|
||||
key: {
|
||||
equals: preferencesKey,
|
||||
},
|
||||
},
|
||||
})) as any as { docs: { value: DocumentPreferences }[] } // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
const initialState = await buildStateFromSchema({
|
||||
id,
|
||||
data: data || {},
|
||||
fieldSchema: formatFields(fields, isEditing),
|
||||
operation: isEditing ? 'update' : 'create',
|
||||
preferences: docPreferences,
|
||||
req,
|
||||
})
|
||||
const initialState = await buildStateFromSchema({
|
||||
id,
|
||||
data: data || {},
|
||||
fieldSchema: formatFields(fields, isEditing),
|
||||
operation: isEditing ? 'update' : 'create',
|
||||
preferences: docPreferences,
|
||||
req,
|
||||
})
|
||||
*/
|
||||
|
||||
const viewComponentProps: ServerSideEditViewProps = {
|
||||
initPageResult,
|
||||
@@ -228,9 +236,9 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
globalSlug={globalConfig?.slug}
|
||||
hasSavePermission={hasSavePermission}
|
||||
id={id}
|
||||
/**
|
||||
initialData={data}
|
||||
initialState={initialState}
|
||||
isEditing={isEditing}
|
||||
title={formatDocTitle({
|
||||
collectionConfig,
|
||||
data,
|
||||
@@ -238,7 +246,8 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
fallback: id?.toString(),
|
||||
globalConfig,
|
||||
i18n,
|
||||
})}
|
||||
})} **/
|
||||
isEditing={isEditing}
|
||||
>
|
||||
{!ViewOverride && (
|
||||
<DocumentHeader
|
||||
|
||||
@@ -546,7 +546,7 @@ export const Form: React.FC<FormProps> = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
void executeOnChange() // eslint-disable-line @typescript-eslint/no-floating-promises
|
||||
if (modified) void executeOnChange()
|
||||
},
|
||||
150,
|
||||
[fields, dispatchFields, onChange],
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
'use client'
|
||||
import type { PaginatedDocs, TypeWithVersion } from 'payload/database'
|
||||
import type { TypeWithTimestamps } from 'payload/types'
|
||||
import type { FormState, TypeWithTimestamps } from 'payload/types'
|
||||
import type { DocumentPermissions, DocumentPreferences, TypeWithID, Where } from 'payload/types'
|
||||
|
||||
import { LoadingOverlay } from '@payloadcms/ui/elements/Loading'
|
||||
import usePayloadAPI from '@payloadcms/ui/hooks/usePayloadAPI'
|
||||
import { formatDocTitle } from '@payloadcms/ui/utilities/formatDocTitle'
|
||||
import { getFormState } from '@payloadcms/ui/utilities/getFormState'
|
||||
import qs from 'qs'
|
||||
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'
|
||||
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import type { DocumentInfoContext, DocumentInfoProps } from './types.js'
|
||||
|
||||
@@ -24,12 +28,15 @@ export const DocumentInfoProvider: React.FC<
|
||||
DocumentInfoProps & {
|
||||
children: React.ReactNode
|
||||
}
|
||||
> = ({ children, ...props }) => {
|
||||
const [documentTitle, setDocumentTitle] = useState(props.title)
|
||||
> = ({ children, initialState: initialStateFromProps, title: titleFromProps, ...props }) => {
|
||||
const [documentTitle, setDocumentTitle] = useState(titleFromProps)
|
||||
|
||||
const [initialState, setInitialState] = useState<FormState>(initialStateFromProps)
|
||||
|
||||
const { id, collectionSlug, globalSlug } = props
|
||||
|
||||
const {
|
||||
admin: { dateFormat },
|
||||
collections,
|
||||
globals,
|
||||
routes: { api },
|
||||
@@ -73,6 +80,66 @@ export const DocumentInfoProvider: React.FC<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Fetching state from Server on the Client. Should be moved back to Document/index.tsx once next allows us to disable the router cache
|
||||
*
|
||||
*
|
||||
*/
|
||||
const hasInitializedState = useRef(!!initialStateFromProps)
|
||||
|
||||
// no need to an additional requests when creating new documents
|
||||
const isEditing = Boolean(id)
|
||||
|
||||
const [{ data, isError, isLoading: isLoadingDocument }] = usePayloadAPI(
|
||||
!hasInitializedState.current
|
||||
? `${baseURL}/${globalSlug ? 'globals/' : ''}${slug}${collectionSlug ? `/${id}` : ''}`
|
||||
: null,
|
||||
{ initialParams: { depth: 0, draft: 'true', 'fallback-locale': 'null' } },
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasInitializedState.current && data) {
|
||||
const getInitialState = async () => {
|
||||
let docPreferences: DocumentPreferences = { fields: {} }
|
||||
|
||||
if (id) {
|
||||
docPreferences = await getPreference(
|
||||
`${id ? 'collection' : 'global'}-${collectionSlug}-${id}`,
|
||||
)
|
||||
}
|
||||
|
||||
const result = await getFormState({
|
||||
apiRoute: api,
|
||||
body: {
|
||||
id,
|
||||
collectionSlug,
|
||||
data: data || {},
|
||||
docPreferences,
|
||||
globalSlug,
|
||||
operation: isEditing ? 'update' : 'create',
|
||||
schemaPath: collectionSlug || globalSlug,
|
||||
},
|
||||
serverURL,
|
||||
})
|
||||
|
||||
setInitialState(result)
|
||||
hasInitializedState.current = true
|
||||
}
|
||||
|
||||
void getInitialState()
|
||||
}
|
||||
}, [api, data, isEditing, collectionSlug, serverURL, id, getPreference, globalSlug])
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Fetching state from Server on the Client DONE
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const getVersions = useCallback(async () => {
|
||||
let versionFetchURL
|
||||
let publishedFetchURL
|
||||
@@ -264,8 +331,21 @@ export const DocumentInfoProvider: React.FC<
|
||||
}, [getVersions])
|
||||
|
||||
useEffect(() => {
|
||||
setDocumentTitle(props.title)
|
||||
}, [props.title])
|
||||
if (titleFromProps) {
|
||||
setDocumentTitle(titleFromProps)
|
||||
} else {
|
||||
setDocumentTitle(
|
||||
formatDocTitle({
|
||||
collectionConfig,
|
||||
data,
|
||||
dateFormat,
|
||||
fallback: id?.toString(),
|
||||
globalConfig,
|
||||
i18n,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}, [collectionConfig, data, dateFormat, i18n, titleFromProps, id, globalConfig])
|
||||
|
||||
useEffect(() => {
|
||||
const loadDocPermissions = async () => {
|
||||
@@ -292,6 +372,12 @@ export const DocumentInfoProvider: React.FC<
|
||||
globalSlug,
|
||||
])
|
||||
|
||||
if (!initialState || isLoadingDocument) {
|
||||
return <LoadingOverlay />
|
||||
}
|
||||
|
||||
if (isError) return null
|
||||
|
||||
const value: DocumentInfoContext = {
|
||||
...props,
|
||||
docConfig,
|
||||
@@ -300,6 +386,7 @@ export const DocumentInfoProvider: React.FC<
|
||||
getDocPreferences,
|
||||
getVersions,
|
||||
hasSavePermission,
|
||||
initialState,
|
||||
onSave: props.onSave,
|
||||
publishedDoc,
|
||||
setDocFieldPreferences,
|
||||
|
||||
@@ -209,6 +209,10 @@ export default buildConfigWithDefaults({
|
||||
type: 'relationship',
|
||||
relationTo: 'users',
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('auth', () => {
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
//await delayNetwork({ context, page, delay: 'Fast 3G' })
|
||||
//await delayNetwork({ context, page, delay: 'Slow 4G' })
|
||||
|
||||
await login({
|
||||
page,
|
||||
|
||||
@@ -25,12 +25,12 @@ const networkConditions = {
|
||||
'Fast 3G': {
|
||||
download: ((1.6 * 1000 * 1000) / 8) * 0.9,
|
||||
upload: ((750 * 1000) / 8) * 0.9,
|
||||
latency: 150 * 3.75,
|
||||
latency: 1000,
|
||||
},
|
||||
'Slow 4G': {
|
||||
download: ((4 * 1000 * 1000) / 8) * 0.8,
|
||||
upload: ((3 * 1000 * 1000) / 8) * 0.8,
|
||||
latency: 20 * 3.75,
|
||||
latency: 1000,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user