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 DefaultView: EditViewComponent
|
||||||
let ErrorView: AdminViewComponent = NotFoundView
|
let ErrorView: AdminViewComponent = NotFoundView
|
||||||
|
|
||||||
|
/**
|
||||||
let data: DocumentType
|
let data: DocumentType
|
||||||
let docPermissions: DocumentPermissions
|
|
||||||
let preferencesKey: string
|
let preferencesKey: string
|
||||||
let fields: Field[]
|
let fields: Field[] **/
|
||||||
|
let docPermissions: DocumentPermissions
|
||||||
let hasSavePermission: boolean
|
let hasSavePermission: boolean
|
||||||
let apiURL: string
|
let apiURL: string
|
||||||
let action: string
|
let action: string
|
||||||
@@ -88,7 +89,8 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
return <NotFoundClient />
|
return <NotFoundClient />
|
||||||
}
|
}
|
||||||
|
|
||||||
fields = collectionConfig.fields
|
/**
|
||||||
|
fields = collectionConfig.fields **/
|
||||||
action = `${serverURL}${apiRoute}/${collectionSlug}${isEditing ? `/${id}` : ''}`
|
action = `${serverURL}${apiRoute}/${collectionSlug}${isEditing ? `/${id}` : ''}`
|
||||||
|
|
||||||
hasSavePermission =
|
hasSavePermission =
|
||||||
@@ -120,6 +122,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
return <ErrorView initPageResult={initPageResult} searchParams={searchParams} />
|
return <ErrorView initPageResult={initPageResult} searchParams={searchParams} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
if (id) {
|
if (id) {
|
||||||
try {
|
try {
|
||||||
data = await payload.findByID({
|
data = await payload.findByID({
|
||||||
@@ -140,13 +143,15 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
|
|
||||||
preferencesKey = `collection-${collectionSlug}-${id}`
|
preferencesKey = `collection-${collectionSlug}-${id}`
|
||||||
}
|
}
|
||||||
|
**/
|
||||||
}
|
}
|
||||||
|
|
||||||
if (globalConfig) {
|
if (globalConfig) {
|
||||||
docPermissions = permissions?.globals?.[globalSlug]
|
docPermissions = permissions?.globals?.[globalSlug]
|
||||||
fields = globalConfig.fields
|
|
||||||
hasSavePermission = isEditing && docPermissions?.update?.permission
|
hasSavePermission = isEditing && docPermissions?.update?.permission
|
||||||
action = `${serverURL}${apiRoute}/globals/${globalSlug}`
|
action = `${serverURL}${apiRoute}/globals/${globalSlug}`
|
||||||
|
/**
|
||||||
|
fields = globalConfig.fields **/
|
||||||
|
|
||||||
apiURL = `${serverURL}${apiRoute}/${globalSlug}?locale=${locale.code}${
|
apiURL = `${serverURL}${apiRoute}/${globalSlug}?locale=${locale.code}${
|
||||||
globalConfig.versions?.drafts ? '&draft=true' : ''
|
globalConfig.versions?.drafts ? '&draft=true' : ''
|
||||||
@@ -172,6 +177,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
return <ErrorView initPageResult={initPageResult} searchParams={searchParams} />
|
return <ErrorView initPageResult={initPageResult} searchParams={searchParams} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
try {
|
try {
|
||||||
data = await payload.findGlobal({
|
data = await payload.findGlobal({
|
||||||
slug: globalSlug,
|
slug: globalSlug,
|
||||||
@@ -188,29 +194,31 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
return <NotFoundClient />
|
return <NotFoundClient />
|
||||||
}
|
}
|
||||||
|
|
||||||
preferencesKey = `global-${globalSlug}`
|
preferencesKey = `global-${globalSlug}` **/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { docs: [{ value: docPreferences } = { value: null }] = [] } = (await payload.find({
|
/**
|
||||||
collection: 'payload-preferences',
|
const { docs: [{ value: docPreferences } = { value: null }] = [] } = (await payload.find({
|
||||||
depth: 0,
|
collection: 'payload-preferences',
|
||||||
limit: 1,
|
depth: 0,
|
||||||
where: {
|
limit: 1,
|
||||||
key: {
|
where: {
|
||||||
equals: preferencesKey,
|
key: {
|
||||||
},
|
equals: preferencesKey,
|
||||||
},
|
},
|
||||||
})) as any as { docs: { value: DocumentPreferences }[] } // eslint-disable-line @typescript-eslint/no-explicit-any
|
},
|
||||||
|
})) as any as { docs: { value: DocumentPreferences }[] } // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
const initialState = await buildStateFromSchema({
|
const initialState = await buildStateFromSchema({
|
||||||
id,
|
id,
|
||||||
data: data || {},
|
data: data || {},
|
||||||
fieldSchema: formatFields(fields, isEditing),
|
fieldSchema: formatFields(fields, isEditing),
|
||||||
operation: isEditing ? 'update' : 'create',
|
operation: isEditing ? 'update' : 'create',
|
||||||
preferences: docPreferences,
|
preferences: docPreferences,
|
||||||
req,
|
req,
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
const viewComponentProps: ServerSideEditViewProps = {
|
const viewComponentProps: ServerSideEditViewProps = {
|
||||||
initPageResult,
|
initPageResult,
|
||||||
@@ -228,9 +236,9 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
globalSlug={globalConfig?.slug}
|
globalSlug={globalConfig?.slug}
|
||||||
hasSavePermission={hasSavePermission}
|
hasSavePermission={hasSavePermission}
|
||||||
id={id}
|
id={id}
|
||||||
|
/**
|
||||||
initialData={data}
|
initialData={data}
|
||||||
initialState={initialState}
|
initialState={initialState}
|
||||||
isEditing={isEditing}
|
|
||||||
title={formatDocTitle({
|
title={formatDocTitle({
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
data,
|
data,
|
||||||
@@ -238,7 +246,8 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
fallback: id?.toString(),
|
fallback: id?.toString(),
|
||||||
globalConfig,
|
globalConfig,
|
||||||
i18n,
|
i18n,
|
||||||
})}
|
})} **/
|
||||||
|
isEditing={isEditing}
|
||||||
>
|
>
|
||||||
{!ViewOverride && (
|
{!ViewOverride && (
|
||||||
<DocumentHeader
|
<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,
|
150,
|
||||||
[fields, dispatchFields, onChange],
|
[fields, dispatchFields, onChange],
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { PaginatedDocs, TypeWithVersion } from 'payload/database'
|
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 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 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'
|
import type { DocumentInfoContext, DocumentInfoProps } from './types.js'
|
||||||
|
|
||||||
@@ -24,12 +28,15 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
DocumentInfoProps & {
|
DocumentInfoProps & {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
> = ({ children, ...props }) => {
|
> = ({ children, initialState: initialStateFromProps, title: titleFromProps, ...props }) => {
|
||||||
const [documentTitle, setDocumentTitle] = useState(props.title)
|
const [documentTitle, setDocumentTitle] = useState(titleFromProps)
|
||||||
|
|
||||||
|
const [initialState, setInitialState] = useState<FormState>(initialStateFromProps)
|
||||||
|
|
||||||
const { id, collectionSlug, globalSlug } = props
|
const { id, collectionSlug, globalSlug } = props
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
admin: { dateFormat },
|
||||||
collections,
|
collections,
|
||||||
globals,
|
globals,
|
||||||
routes: { api },
|
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 () => {
|
const getVersions = useCallback(async () => {
|
||||||
let versionFetchURL
|
let versionFetchURL
|
||||||
let publishedFetchURL
|
let publishedFetchURL
|
||||||
@@ -264,8 +331,21 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
}, [getVersions])
|
}, [getVersions])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDocumentTitle(props.title)
|
if (titleFromProps) {
|
||||||
}, [props.title])
|
setDocumentTitle(titleFromProps)
|
||||||
|
} else {
|
||||||
|
setDocumentTitle(
|
||||||
|
formatDocTitle({
|
||||||
|
collectionConfig,
|
||||||
|
data,
|
||||||
|
dateFormat,
|
||||||
|
fallback: id?.toString(),
|
||||||
|
globalConfig,
|
||||||
|
i18n,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [collectionConfig, data, dateFormat, i18n, titleFromProps, id, globalConfig])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDocPermissions = async () => {
|
const loadDocPermissions = async () => {
|
||||||
@@ -292,6 +372,12 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
globalSlug,
|
globalSlug,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
if (!initialState || isLoadingDocument) {
|
||||||
|
return <LoadingOverlay />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) return null
|
||||||
|
|
||||||
const value: DocumentInfoContext = {
|
const value: DocumentInfoContext = {
|
||||||
...props,
|
...props,
|
||||||
docConfig,
|
docConfig,
|
||||||
@@ -300,6 +386,7 @@ export const DocumentInfoProvider: React.FC<
|
|||||||
getDocPreferences,
|
getDocPreferences,
|
||||||
getVersions,
|
getVersions,
|
||||||
hasSavePermission,
|
hasSavePermission,
|
||||||
|
initialState,
|
||||||
onSave: props.onSave,
|
onSave: props.onSave,
|
||||||
publishedDoc,
|
publishedDoc,
|
||||||
setDocFieldPreferences,
|
setDocFieldPreferences,
|
||||||
|
|||||||
@@ -209,6 +209,10 @@ export default buildConfigWithDefaults({
|
|||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
relationTo: 'users',
|
relationTo: 'users',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'text',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe('auth', () => {
|
|||||||
page = await context.newPage()
|
page = await context.newPage()
|
||||||
initPageConsoleErrorCatch(page)
|
initPageConsoleErrorCatch(page)
|
||||||
|
|
||||||
//await delayNetwork({ context, page, delay: 'Fast 3G' })
|
//await delayNetwork({ context, page, delay: 'Slow 4G' })
|
||||||
|
|
||||||
await login({
|
await login({
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ const networkConditions = {
|
|||||||
'Fast 3G': {
|
'Fast 3G': {
|
||||||
download: ((1.6 * 1000 * 1000) / 8) * 0.9,
|
download: ((1.6 * 1000 * 1000) / 8) * 0.9,
|
||||||
upload: ((750 * 1000) / 8) * 0.9,
|
upload: ((750 * 1000) / 8) * 0.9,
|
||||||
latency: 150 * 3.75,
|
latency: 1000,
|
||||||
},
|
},
|
||||||
'Slow 4G': {
|
'Slow 4G': {
|
||||||
download: ((4 * 1000 * 1000) / 8) * 0.8,
|
download: ((4 * 1000 * 1000) / 8) * 0.8,
|
||||||
upload: ((3 * 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