Skip to content

Conversation

@jacobsfletch
Copy link
Member

@jacobsfletch jacobsfletch commented Sep 23, 2024

Currently, Payload renders all custom components on initial compile of the admin panel. This is problematic for two key reasons:

  1. Custom components do not receive contextual data, i.e. fields do not receive their field data, edit views do not receive their document data, etc.
  2. Components are unnecessarily rendered before they are used

This was initially required to support React Server Components within the Payload Admin Panel for a few reasons:

  1. Fields can be dynamically rendered within arrays, blocks, etc.
  2. Documents can be recursively rendered within a "drawer" UI, i.e. relationship fields
  3. Payload supports server/client component composition

In order to achieve this, components need to be rendered on the server and passed as "slots" to the client. Currently, the pattern for this is to render custom server components in the "client config". Then when a view or field is needed to be rendered, we first check the client config for a "pre-rendered" component, otherwise render our client-side fallback component.

But for the reasons listed above, this pattern doesn't exactly make custom server components very useful within the Payload Admin Panel, which is where this PR comes in. Now, instead of pre-rendering all components on initial compile, we're able to render custom components on demand, only as they are needed.

To achieve this, we've established this pattern of React Server Functions in the Payload Admin Panel. With Server Functions, we can iterate the Payload Config and return JSX through React's text/x-component content-type. This means we're able to pass contextual props to custom components, such as data for fields and views.

Breaking Changes

  1. Add the following to your root layout file, typically located at (app)/(payload)/layout.tsx:

    /* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
    /* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
    + import type { ServerFunctionClient } from 'payload'
    
    import config from '@payload-config'
    import { RootLayout } from '@payloadcms/next/layouts'
    import { handleServerFunctions } from '@payloadcms/next/utilities'
    import React from 'react'
    
    import { importMap } from './admin/importMap.js'
    import './custom.scss'
    
    type Args = {
      children: React.ReactNode
    }
    
    + const serverFunctions: ServerFunctionClient = async function (args) {
    +  'use server'
    +  return handleServerFunctions({
    +    ...args,
    +    config,
    +    importMap,
    +  })
    + }
    
    const Layout = ({ children }: Args) => (
      <RootLayout
        config={config}
        importMap={importMap}
    +  serverFunctions={serverFunctions}
      >
        {children}
      </RootLayout>
    )
    
    export default Layout
  2. If you were previously posting to the /api/form-state endpoint, it no longer exists. Instead, you'll need to invoke the form-state Server Function, which can be done through the new getFormState utility:

    - import { getFormState } from '@payloadcms/ui'
    - const { state } = await getFormState({
    -   apiRoute: '',
    -   body: {
    -     // ...
    -   },
    -   serverURL: ''
    - })
    
    + const { getFormState } = useServerFunctions()
    +
    + const { state } = await getFormState({
    +   // ...
    + })
  3. Multiple layer of React Context were removed in favor of direct props. As a result, the following React hooks were removed:

    - useFieldProps()
    - useTableCell()

    If you were previously using any of these hooks, for example to access field path or cellData, you can now access that directly from the props object.

    - const { path } = useFieldProps();
    + const { path } = props;
    
    - const { cellData } = useTableCell();
    + const { cellData } = props;

    The field prop also no longer contains a _schemaPath property. Instead, this is now also accessed directly through props:

    - const { _schemaPath } = props.field
    + const { schemaPath } = props

AlessioGr and others added 25 commits November 4, 2024 06:38
…Submit form hooks, leading to server action errors as client components were passed to the server action
@socket-security
Copy link

socket-security bot commented Nov 11, 2024

No dependency changes detected. Learn more about Socket for GitHub ↗︎

👍 No dependency changes detected in pull request

@jmikrut jmikrut merged commit c96fa61 into beta Nov 11, 2024
3 of 4 checks passed
@jmikrut jmikrut deleted the feat/on-demand-rsc branch November 11, 2024 18:59
@github-actions
Copy link
Contributor

🚀 This is included in version v3.0.0-beta.128

@rburgst
Copy link

rburgst commented Nov 18, 2024

the multitenant sample is now broken, it would be cool if this could be fixed

jacobsfletch added a commit that referenced this pull request Nov 26, 2024
When using the `admin.hidden: true` property on a collection, it
rightfully removes all navigation and routing for that particular
collection. However, this also affects the expected behavior of hidden
entities when they are rendered within a drawer, such as the document
drawer or list drawer. For example, when creating a new _admin.hidden_
document through the relationship or join field, the drawer should still
render the view, despite the underlying route for that view being
disabled. This change was a result of the introduction of on-demand
server components in #8364, where we now make a server roundtrip to
render the view in its entirety, which include the logic that redirects
these hidden entities.

Now, we pass a new `overrideEntityVisibility` argument through the
server function that, when true, skips this step. This way documents can
continue to respect `admin.hidden` while also having the ability to
override on a case-by-case basis throughout the UI.
kendelljoseph pushed a commit that referenced this pull request Feb 21, 2025
When using the `admin.hidden: true` property on a collection, it
rightfully removes all navigation and routing for that particular
collection. However, this also affects the expected behavior of hidden
entities when they are rendered within a drawer, such as the document
drawer or list drawer. For example, when creating a new _admin.hidden_
document through the relationship or join field, the drawer should still
render the view, despite the underlying route for that view being
disabled. This change was a result of the introduction of on-demand
server components in #8364, where we now make a server roundtrip to
render the view in its entirety, which include the logic that redirects
these hidden entities.

Now, we pass a new `overrideEntityVisibility` argument through the
server function that, when true, skips this step. This way documents can
continue to respect `admin.hidden` while also having the ability to
override on a case-by-case basis throughout the UI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants