Skip to content

Commit

Permalink
perf!: removes unnecessary field styles from initial page response (#…
Browse files Browse the repository at this point in the history
…9286)

Optimizes initial page responses by removing unnecessary inline field
styles that were being sent through the HTML response. The Client Config
contains a large number of duplicates of the string:
`"style\":{\"flex\":\"1 1 auto\"}`, one for every single field within
the entirely of the config. This leads to hundreds or potentially
thousands of instances of this same string, depending on the number of
fields within the config itself. This is regardless of custom field
widths being defined. Instead, we can do this entirely client-side,
preventing this string from ever being transmitted over the network in
the first place.

## Breaking Changes

This only effects those who are importing Payload's field components
into your own Custom Components or front-end application. The `width`
prop no longer exists. It has been consolidated into the existing
`style` prop. To migrate, simply move this prop as follows:

```diff
import { TextInput } from '@payloadcms/ui

export const MyCustomComponent = () => {
  return (
    <TextInput 
-      width="60%"
       style={{
+        width: "60%,
       }}
    />
  )
}
```
  • Loading branch information
jacobsfletch authored Nov 18, 2024
1 parent 665b353 commit 30947d2
Show file tree
Hide file tree
Showing 28 changed files with 159 additions and 201 deletions.
19 changes: 0 additions & 19 deletions packages/payload/src/fields/config/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,6 @@ export const createClientField = ({
clientField.label = incomingField.label({ t: i18n.t })
}

if (!(clientField.admin instanceof Object)) {
clientField.admin = {} as AdminClient
}

if ('admin' in incomingField && 'width' in incomingField.admin) {
clientField.admin.style = {
...clientField.admin.style,
'--field-width': clientField.admin.width,
}

delete clientField.admin.style.width // avoid needlessly adding this to the element's style attribute
} else {
if (!(clientField.admin.style instanceof Object)) {
clientField.admin.style = {}
}

clientField.admin.style.flex = '1 1 auto'
}

switch (incomingField.type) {
case 'array':
case 'collapsible':
Expand Down
17 changes: 7 additions & 10 deletions packages/richtext-lexical/src/field/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import type { EditorState, SerializedEditorState } from 'lexical'

import { FieldLabel, useEditDepth, useField, withCondition } from '@payloadcms/ui'
import React, { useCallback } from 'react'
import { mergeFieldStyles } from '@payloadcms/ui/shared'
import React, { useCallback, useMemo } from 'react'
import { ErrorBoundary } from 'react-error-boundary'

import type { SanitizedClientEditorConfig } from '../lexical/config/types.js'
Expand All @@ -22,9 +23,10 @@ const RichTextComponent: React.FC<
> = (props) => {
const {
editorConfig,
field,
field: {
name,
admin: { className, readOnly: readOnlyFromAdmin, style, width } = {},
admin: { className, readOnly: readOnlyFromAdmin } = {},
label,
localized,
required,
Expand Down Expand Up @@ -87,15 +89,10 @@ const RichTextComponent: React.FC<
[setValue],
)

const styles = useMemo(() => mergeFieldStyles(field), [field])

return (
<div
className={classes}
key={pathWithEditDepth}
style={{
...style,
width,
}}
>
<div className={classes} key={pathWithEditDepth} style={styles}>
{Error}
{Label || <FieldLabel label={label} localized={localized} required={required} />}
<div className={`${baseClass}__wrap`}>
Expand Down
16 changes: 7 additions & 9 deletions packages/richtext-slate/src/field/RichText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ReactEditor } from 'slate-react'

import { getTranslation } from '@payloadcms/translations'
import { FieldLabel, useEditDepth, useField, useTranslation, withCondition } from '@payloadcms/ui'
import { mergeFieldStyles } from '@payloadcms/ui/shared'
import { isHotkey } from 'is-hotkey'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { createEditor, Node, Element as SlateElement, Text, Transforms } from 'slate'
Expand All @@ -19,8 +20,8 @@ import type { LoadedSlateFieldProps } from './types.js'
import { defaultRichTextValue } from '../data/defaultValue.js'
import { richTextValidate } from '../data/validation.js'
import { listTypes } from './elements/listTypes.js'
import { hotkeys } from './hotkeys.js'
import './index.scss'
import { hotkeys } from './hotkeys.js'
import { toggleLeaf } from './leaves/toggle.js'
import { withEnterBreakOut } from './plugins/withEnterBreakOut.js'
import { withHTML } from './plugins/withHTML.js'
Expand All @@ -42,9 +43,10 @@ declare module 'slate' {
const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
const {
elements,
field,
field: {
name,
admin: { className, placeholder, readOnly: readOnlyFromAdmin, style, width } = {},
admin: { className, placeholder, readOnly: readOnlyFromAdmin } = {},
label,
required,
},
Expand Down Expand Up @@ -274,6 +276,8 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
// }
// }, [path, editor]);

const styles = useMemo(() => mergeFieldStyles(field), [field])

const classes = [
baseClass,
'field-type',
Expand All @@ -300,13 +304,7 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
}

return (
<div
className={classes}
style={{
...style,
width,
}}
>
<div className={classes} style={styles}>
{Label || <FieldLabel label={label} required={required} />}
<div className={`${baseClass}__wrap`}>
{Error}
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/exports/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { getInitialColumns } from '../../elements/TableColumns/getInitialColumns
export { Translation } from '../../elements/Translation/index.js'
export { withMergedProps } from '../../elements/withMergedProps/index.js' // cannot be within a 'use client', thus we export this from shared
export { WithServerSideProps } from '../../elements/WithServerSideProps/index.js'
export { mergeFieldStyles } from '../../fields/mergeFieldStyles.js'
export { reduceToSerializableFields } from '../../forms/Form/reduceToSerializableFields.js'
export { PayloadIcon } from '../../graphics/Icon/index.js'
export { PayloadLogo } from '../../graphics/Logo/index.js'
Expand Down
21 changes: 8 additions & 13 deletions packages/ui/src/fields/Checkbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
CheckboxFieldValidation,
} from 'payload'

import React, { useCallback } from 'react'
import React, { useCallback, useMemo } from 'react'

import type { CheckboxInputProps } from './Input.js'

Expand All @@ -17,8 +17,9 @@ import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js'
import { useEditDepth } from '../../providers/EditDepth/index.js'
import { generateFieldID } from '../../utilities/generateFieldID.js'
import { fieldBaseClass } from '../shared/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
import { CheckboxInput } from './Input.js'

const baseClass = 'checkbox'
Expand All @@ -30,14 +31,9 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => {
id,
checked: checkedFromProps,
disableFormData,
field,
field: {
name,
admin: {
className,
description,
style,
width,
} = {} as CheckboxFieldClientProps['field']['admin'],
admin: { className, description } = {} as CheckboxFieldClientProps['field']['admin'],
label,
required,
} = {} as CheckboxFieldClientProps['field'],
Expand Down Expand Up @@ -85,6 +81,8 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => {

const fieldID = id || generateFieldID(path, editDepth, uuid)

const styles = useMemo(() => mergeFieldStyles(field), [field])

return (
<div
className={[
Expand All @@ -97,10 +95,7 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => {
]
.filter(Boolean)
.join(' ')}
style={{
...style,
width,
}}
style={styles}
>
<RenderCustomComponent
CustomComponent={Error}
Expand Down
23 changes: 8 additions & 15 deletions packages/ui/src/fields/Code/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'
import type { CodeFieldClientComponent } from 'payload'

import React, { useCallback } from 'react'
import React, { useCallback, useMemo } from 'react'

import { CodeEditor } from '../../elements/CodeEditor/index.js'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
Expand All @@ -10,8 +10,9 @@ import { FieldError } from '../../fields/FieldError/index.js'
import { FieldLabel } from '../../fields/FieldLabel/index.js'
import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js'
import { fieldBaseClass } from '../shared/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss'
import { fieldBaseClass } from '../shared/index.js'

const prismToMonacoLanguageMap = {
js: 'javascript',
Expand All @@ -22,16 +23,9 @@ const baseClass = 'code-field'

const CodeFieldComponent: CodeFieldClientComponent = (props) => {
const {
field,
field: {
name,
admin: {
className,
description,
editorOptions = {},
language = 'javascript',
style,
width,
} = {},
admin: { className, description, editorOptions = {}, language = 'javascript' } = {},
label,
localized,
required,
Expand Down Expand Up @@ -60,6 +54,8 @@ const CodeFieldComponent: CodeFieldClientComponent = (props) => {
validate: memoizedValidate,
})

const styles = useMemo(() => mergeFieldStyles(field), [field])

return (
<div
className={[
Expand All @@ -71,10 +67,7 @@ const CodeFieldComponent: CodeFieldClientComponent = (props) => {
]
.filter(Boolean)
.join(' ')}
style={{
...style,
width,
}}
style={styles}
>
<RenderCustomComponent
CustomComponent={Label}
Expand Down
16 changes: 7 additions & 9 deletions packages/ui/src/fields/Collapsible/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client'
import type { AdminClient, CollapsibleFieldClientComponent, DocumentPreferences } from 'payload'
import type { CollapsibleFieldClientComponent, DocumentPreferences } from 'payload'

import { getTranslation } from '@payloadcms/translations'
import React, { Fragment, useCallback, useEffect, useState } from 'react'
import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'

import { Collapsible as CollapsibleElement } from '../../elements/Collapsible/index.js'
import { ErrorPill } from '../../elements/ErrorPill/index.js'
Expand All @@ -16,8 +16,9 @@ import { withCondition } from '../../forms/withCondition/index.js'
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { usePreferences } from '../../providers/Preferences/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { fieldBaseClass } from '../shared/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss'
import { fieldBaseClass } from '../shared/index.js'

const baseClass = 'collapsible-field'

Expand Down Expand Up @@ -98,15 +99,12 @@ const CollapsibleFieldComponent: CollapsibleFieldClientComponent = (props) => {
void fetchInitialState()
}, [getPreference, preferencesKey, fieldPreferencesKey, initCollapsed, path])

const styles = useMemo(() => mergeFieldStyles(field), [field])

if (typeof collapsedOnMount !== 'boolean') {
return null
}

const style: AdminClient['style'] = {
...field.admin?.style,
'--field-width': field.admin.width,
}

return (
<Fragment>
<WatchChildErrors fields={fields} path={path.split('.')} setErrorCount={setErrorCount} />
Expand All @@ -120,7 +118,7 @@ const CollapsibleFieldComponent: CollapsibleFieldClientComponent = (props) => {
.filter(Boolean)
.join(' ')}
id={`field-${fieldPreferencesKey}`}
style={style}
style={styles}
>
<CollapsibleElement
className={`${baseClass}__collapsible`}
Expand Down
16 changes: 8 additions & 8 deletions packages/ui/src/fields/DateTime/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { DateFieldClientComponent, DateFieldValidation } from 'payload'

import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react'
import React, { useCallback, useMemo } from 'react'

import { DatePickerField } from '../../elements/DatePicker/index.js'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
Expand All @@ -12,16 +12,17 @@ import { FieldLabel } from '../../fields/FieldLabel/index.js'
import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { fieldBaseClass } from '../shared/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss'
import { fieldBaseClass } from '../shared/index.js'

const baseClass = 'date-time-field'

const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
const {
field,
field: {
name,
admin: { className, date: datePickerProps, description, placeholder, style, width } = {},
admin: { className, date: datePickerProps, description, placeholder } = {},
label,
localized,
required,
Expand Down Expand Up @@ -52,6 +53,8 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
validate: memoizedValidate,
})

const styles = useMemo(() => mergeFieldStyles(field), [field])

return (
<div
className={[
Expand All @@ -63,10 +66,7 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
]
.filter(Boolean)
.join(' ')}
style={{
...style,
width,
}}
style={styles}
>
<RenderCustomComponent
CustomComponent={Label}
Expand Down
16 changes: 7 additions & 9 deletions packages/ui/src/fields/Email/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
} from 'payload'

import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react'
import React, { useCallback, useMemo } from 'react'

import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { FieldDescription } from '../../fields/FieldDescription/index.js'
Expand All @@ -15,20 +15,19 @@ import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { FieldLabel } from '../FieldLabel/index.js'
import { fieldBaseClass } from '../shared/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss'
import { fieldBaseClass } from '../shared/index.js'

const EmailFieldComponent: EmailFieldClientComponent = (props) => {
const {
field,
field: {
name,
admin: {
autoComplete,
className,
description,
placeholder,
style,
width,
} = {} as EmailFieldClientProps['field']['admin'],
label,
localized,
Expand Down Expand Up @@ -60,15 +59,14 @@ const EmailFieldComponent: EmailFieldClientComponent = (props) => {
validate: memoizedValidate,
})

const styles = useMemo(() => mergeFieldStyles(field), [field])

return (
<div
className={[fieldBaseClass, 'email', className, showError && 'error', readOnly && 'read-only']
.filter(Boolean)
.join(' ')}
style={{
...style,
width,
}}
style={styles}
>
<RenderCustomComponent
CustomComponent={Label}
Expand Down
Loading

0 comments on commit 30947d2

Please sign in to comment.