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:
Alessio Gravili
2024-03-21 16:38:12 -04:00
committed by GitHub
parent 11cbb774bc
commit 35b0d213a6
6 changed files with 135 additions and 35 deletions

View File

@@ -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,10 +194,11 @@ 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,
@@ -211,6 +218,7 @@ export const Document: React.FC<AdminViewProps> = async ({
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

View File

@@ -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],

View File

@@ -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,

View File

@@ -209,6 +209,10 @@ export default buildConfigWithDefaults({
type: 'relationship',
relationTo: 'users',
},
{
name: 'text',
type: 'text',
},
],
},
],

View File

@@ -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,

View File

@@ -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,
},
}