This PR makes three major changes to the codebase: 1. [Component Paths](#component-paths) Instead of importing custom components into your config directly, they are now defined as file paths and rendered only when needed. That way the Payload config will be significantly more lightweight, and ensures that the Payload config is 100% server-only and Node-safe. Related discussion: https://github.com/payloadcms/payload/discussions/6938 2. [Client Config](#client-config) Deprecates the component map by merging its logic into the client config. The main goal of this change is for performance and simplification. There was no need to deeply iterate over the Payload config twice, once for the component map, and another for the client config. Instead, we can do everything in the client config one time. This has also dramatically simplified the client side prop drilling through the UI library. Now, all components can share the same client config which matches the exact shape of their Payload config (with the exception of non-serializable props and mapped custom components). 3. [Custom client component are no longer server-rendered](#custom-client-components-are-no-longer-server-rendered) Previously, custom components would be server-rendered, no matter if they are server or client components. Now, only server components are rendered on the server. Client components are automatically detected, and simply get passed through as `MappedComponent` to be rendered fully client-side. ## Component Paths Instead of importing custom components into your config directly, they are now defined as file paths and rendered only when needed. That way the Payload config will be significantly more lightweight, and ensures that the Payload config is 100% server-only and Node-safe. Related discussion: https://github.com/payloadcms/payload/discussions/6938 In order to reference any custom components in the Payload config, you now have to specify a string path to the component instead of importing it. Old: ```ts import { MyComponent2} from './MyComponent2.js' admin: { components: { Label: MyComponent2 }, }, ``` New: ```ts admin: { components: { Label: '/collections/Posts/MyComponent2.js#MyComponent2', // <= has to be a relative path based on a baseDir configured in the Payload config - NOT relative based on the importing file }, }, ``` ### Local API within Next.js routes Previously, if you used the Payload Local API within Next.js pages, all the client-side modules are being added to the bundle for that specific page, even if you only need server-side functionality. This `/test` route, which uses the Payload local API, was previously 460 kb. It is now down to 91 kb and does not bundle the Payload client-side admin panel anymore. All tests done [here](https://github.com/payloadcms/payload-3.0-demo/tree/feat/path-test) with beta.67/PR, db-mongodb and default richtext-lexical: **dev /admin before:**  **dev /admin after:**  --- **dev /test before:**  **dev /test after:**  --- **build before:**  **build after::**  ### Usage of the Payload Local API / config outside of Next.js This will make it a lot easier to use the Payload config / local API in other, server-side contexts. Previously, you might encounter errors due to client files (like .scss files) not being allowed to be imported. ## Client Config Deprecates the component map by merging its logic into the client config. The main goal of this change is for performance and simplification. There was no need to deeply iterate over the Payload config twice, once for the component map, and another for the client config. Instead, we can do everything in the client config one time. This has also dramatically simplified the client side prop drilling through the UI library. Now, all components can share the same client config which matches the exact shape of their Payload config (with the exception of non-serializable props and mapped custom components). This is breaking change. The `useComponentMap` hook no longer exists, and most component props have changed (for the better): ```ts const { componentMap } = useComponentMap() // old const { config } = useConfig() // new ``` The `useConfig` hook has also changed in shape, `config` is now a property _within_ the context obj: ```ts const config = useConfig() // old const { config } = useConfig() // new ``` ## Custom Client Components are no longer server rendered Previously, custom components would be server-rendered, no matter if they are server or client components. Now, only server components are rendered on the server. Client components are automatically detected, and simply get passed through as `MappedComponent` to be rendered fully client-side. The benefit of this change: Custom client components can now receive props. Previously, the only way for them to receive dynamic props from a parent client component was to use hooks, e.g. `useFieldProps()`. Now, we do have the option of passing in props to the custom components directly, if they are client components. This will be simpler than having to look for the correct hook. This makes rendering them on the client a little bit more complex, as you now have to check if that component is a server component (=> already has been rendered) or a client component (=> not rendered yet, has to be rendered here). However, this added complexity has been alleviated through the easy-to-use `<RenderMappedComponent />` helper. This helper now also handles rendering arrays of custom components (e.g. beforeList, beforeLogin ...), which actually makes rendering custom components easier in some cases. ## Misc improvements This PR includes misc, breaking changes. For example, we previously allowed unions between components and config object for the same property. E.g. for the custom view property, you were allowed to pass in a custom component or an object with other properties, alongside a custom component. Those union types are now gone. You can now either pass an object, or a component. The previous `{ View: MyViewComponent}` is now `{ View: { Component: MyViewComponent} }` or `{ View: { Default: { Component: MyViewComponent} } }`. This dramatically simplifies the way we read & process those properties, especially in buildComponentMap. We can now simply check for the existence of one specific property, which always has to be a component, instead of running cursed runtime checks on a shared union property which could contain a component, but could also contain functions or objects.   - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. --------- Co-authored-by: PatrikKozak <patrik@payloadcms.com> Co-authored-by: Paul <paul@payloadcms.com> Co-authored-by: Paul Popus <paul@nouance.io> Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com> Co-authored-by: James <james@trbl.design>
387 lines
16 KiB
Plaintext
387 lines
16 KiB
Plaintext
---
|
|
title: Swap in your own React components
|
|
label: Custom Components
|
|
order: 40
|
|
desc: Fully customize your Admin Panel by swapping in your own React components. Add fields, remove views, update routes and change functions to sculpt your perfect Dashboard.
|
|
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
|
---
|
|
|
|
The Payload [Admin Panel](./overview) is designed to be as minimal and straightforward as possible to allow for both easy customization and full control over the UI. In order for Payload to support this level of customization, Payload provides a pattern for you to supply your own React components through your [Payload Config](../configuration/overview).
|
|
|
|
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default, with the exception of [Custom Providers](#custom-providers). This enables the use of the [Local API](../local-api) directly on the front-end. Custom Components are available for nearly every part of the Admin Panel for extreme granularity and control.
|
|
|
|
<Banner type="success">
|
|
<strong>Note:</strong>
|
|
Client Components continue to be fully supported. To use Client Components in your app, simply include the `use client` directive. Payload will automatically detect and remove all default, [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types) before rendering your component. [More details](#client-components).
|
|
</Banner>
|
|
|
|
There are four main types of Custom Components in Payload:
|
|
|
|
- [Root Components](#root-components)
|
|
- [Collection Components](./collections#components)
|
|
- [Global Components](./globals#components)
|
|
- [Field Components](./fields)
|
|
|
|
To swap in your own Custom Component, consult the list of available components. Determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-components) accordingly.
|
|
|
|
## Root Components
|
|
|
|
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo or the main nav.
|
|
|
|
To override Root Components, use the `admin.components` property in your [Payload Config](../getting-started/overview):
|
|
|
|
```ts
|
|
import { buildConfig } from 'payload'
|
|
|
|
export default buildConfig({
|
|
// ...
|
|
admin: {
|
|
// highlight-start
|
|
components: {
|
|
// ...
|
|
},
|
|
// highlight-end
|
|
},
|
|
})
|
|
```
|
|
|
|
_For details on how to build Custom Components, see [Building Custom Components](#building-custom-components)._
|
|
|
|
The following options are available:
|
|
|
|
| Path | Description |
|
|
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| **`Nav`** | Contains the sidebar / mobile menu in its entirety. |
|
|
| **`beforeNavLinks`** | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. |
|
|
| **`afterNavLinks`** | An array of Custom Components to inject into the built-in Nav, _after_ the links. |
|
|
| **`beforeDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
|
|
| **`afterDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. |
|
|
| **`beforeLogin`** | An array of Custom Components to inject into the built-in Login, _before_ the default login form. |
|
|
| **`afterLogin`** | An array of Custom Components to inject into the built-in Login, _after_ the default login form. |
|
|
| **`logout.Button`** | The button displayed in the sidebar that logs the user out. |
|
|
| **`graphics.Icon`** | The simplified logo used in contexts like the the `Nav` component. |
|
|
| **`graphics.Logo`** | The full logo used in contexts like the `Login` view. |
|
|
| **`providers`** | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](#custom-providers). |
|
|
| **`actions`** | An array of Custom Components to be rendered in the header of the Admin Panel, providing additional interactivity and functionality. |
|
|
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
|
|
|
|
<Banner type="success">
|
|
<strong>Note:</strong>
|
|
You can also use set [Collection Components](./collections#components) and [Global Components](./globals#components) in their respective configs.
|
|
</Banner>
|
|
|
|
### Custom Providers
|
|
|
|
As you add more and more Custom Components to your [Admin Panel](./overview), you may find it helpful to add additional [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context)(s). Payload allows you to inject your own context providers in your app so you can export your own custom hooks, etc.
|
|
|
|
To add a Custom Provider, use the `admin.components.providers` property in your [Payload Config](../getting-started/overview):
|
|
|
|
```ts
|
|
import { buildConfig } from 'payload'
|
|
|
|
export default buildConfig({
|
|
// ...
|
|
admin: {
|
|
components: {
|
|
providers: ['/path/to/MyProvider'], // highlight-line
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
Then build your Custom Provider as follows:
|
|
|
|
```tsx
|
|
'use client'
|
|
import React, { createContext, useContext } from 'react'
|
|
|
|
const MyCustomContext = React.createContext(myCustomValue)
|
|
|
|
export const MyProvider: React.FC = ({ children }) => {
|
|
return (
|
|
<MyCustomContext.Provider value={myCustomValue}>
|
|
{children}
|
|
</MyCustomContext.Provider>
|
|
)
|
|
}
|
|
|
|
export const useMyCustomContext = () => useContext(MyCustomContext)
|
|
```
|
|
|
|
<Banner type="warning">
|
|
<strong>Reminder:</strong> Custom Providers are by definition Client Components. This means they must include the `use client` directive at the top of their files and cannot use server-only code.
|
|
</Banner>
|
|
|
|
## Building Custom Components
|
|
|
|
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default, with the exception of [Custom Providers](#custom-providers). This enables the use of the [Local API](../local-api) directly on the front-end, among other things.
|
|
|
|
To make building Custom Components as easy as possible, Payload automatically provides common props, such as the [`payload`](../local-api/overview) class and the [`i18n`](../configuration/i18n) object. This means that when building Custom Components within the Admin Panel, you do not have to get these yourself.
|
|
|
|
Here is an example:
|
|
|
|
```tsx
|
|
import React from 'react'
|
|
|
|
const MyServerComponent = async ({
|
|
payload // highlight-line
|
|
}) => {
|
|
const page = await payload.findByID({
|
|
collection: 'pages',
|
|
id: '123',
|
|
})
|
|
|
|
return (
|
|
<p>{page.title}</p>
|
|
)
|
|
}
|
|
```
|
|
|
|
Each Custom Component receives the following props by default:
|
|
|
|
| Prop | Description |
|
|
| ------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
| `payload` | The [Payload](../local-api/overview) class. |
|
|
| `i18n` | The [i18n](../i18n) object. |
|
|
|
|
Custom Components also receive various other props that are specific to the context in which the Custom Component is being rendered. For example, [Custom Views](./views) receive the `user` prop. For a full list of available props, consult the documentation related to the specific component you are working with.
|
|
|
|
<Banner type="success">
|
|
See [Root Components](#root-components), [Collection Components](#collection-components), [Global Components](#global-components), or [Field Components](#custom-field-components) for a complete list of all available components.
|
|
</Banner>
|
|
|
|
### Client Components
|
|
|
|
When [Building Custom Components](#building-custom-components), it's still possible to use client-side code such as `useState` or the `window` object. To do this, simply add the `use client` directive at the top of your file. Payload will automatically detect and remove all default, [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types) before rendering your component.
|
|
|
|
```tsx
|
|
'use client' // highlight-line
|
|
import React, { useState } from 'react'
|
|
|
|
export const MyClientComponent: React.FC = () => {
|
|
const [count, setCount] = useState(0)
|
|
|
|
return (
|
|
<button onClick={() => setCount(count + 1)}>
|
|
Clicked {count} times
|
|
</button>
|
|
)
|
|
}
|
|
```
|
|
|
|
<Banner type="warning">
|
|
<strong>Reminder:</strong>
|
|
Client Components cannot be passed [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types). If you are rendering your Client Component _from within_ a Server Component, ensure that its props are serializable.
|
|
</Banner>
|
|
|
|
### Accessing the Payload Config
|
|
|
|
From any Server Component, the [Payload Config](../configuration/overview) can be accessed directly from the `payload` prop:
|
|
|
|
```tsx
|
|
import React from 'react'
|
|
|
|
export default async function MyServerComponent({
|
|
payload: {
|
|
config // highlight-line
|
|
}
|
|
}) {
|
|
return (
|
|
<Link href={config.serverURL}>
|
|
Go Home
|
|
</Link>
|
|
)
|
|
}
|
|
```
|
|
|
|
But, the Payload Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types) by design. It is full of custom validation functions, React components, etc. This means that the Payload Config, in its entirety, cannot be passed directly to Client Components.
|
|
|
|
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](./hooks#useconfig) hook:
|
|
|
|
```tsx
|
|
import React from 'react'
|
|
import { useConfig } from '@payloadcms/ui'
|
|
|
|
export const MyClientComponent: React.FC = () => {
|
|
const { config: { serverURL } } = useConfig() // highlight-line
|
|
|
|
return (
|
|
<Link href={serverURL}>
|
|
Go Home
|
|
</Link>
|
|
)
|
|
}
|
|
```
|
|
|
|
<Banner type="success">
|
|
See [Using Hooks](#using-hooks) for more details.
|
|
</Banner>
|
|
|
|
All [Field Components](./fields) automatically receive their respective Client Field Config through a common [`field`](./fields#the-field-prop) prop:
|
|
|
|
```tsx
|
|
'use client'
|
|
import React from 'react'
|
|
import type { TextFieldProps } from 'payload'
|
|
|
|
export const MyClientFieldComponent: TextFieldProps = ({ field: { name } }) => {
|
|
return (
|
|
<p>
|
|
{`This field's name is ${name}`}
|
|
</p>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Using Hooks
|
|
|
|
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](./hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts:
|
|
|
|
```tsx
|
|
'use client'
|
|
import React from 'react'
|
|
import { useDocumentInfo } from '@payloadcms/ui'
|
|
|
|
export const MyClientComponent: React.FC = () => {
|
|
const { slug } = useDocumentInfo() // highlight-line
|
|
|
|
return (
|
|
<p>{`Entity slug: ${slug}`}</p>
|
|
)
|
|
}
|
|
```
|
|
|
|
<Banner type="success">
|
|
See the [Hooks](./hooks) documentation for a full list of available hooks.
|
|
</Banner>
|
|
|
|
### Getting the Current Language
|
|
|
|
All Custom Components can support multiple languages to be consistent with Payload's [Internationalization](../configuration/i18n). To do this, first add your translation resources to the [I18n Config](../configuration/i18n).
|
|
|
|
From any Server Component, you can translate resources using the `getTranslation` function from `@payloadcms/translations`. All Server Components automatically receive the `i18n` object as a prop by default.
|
|
|
|
```tsx
|
|
import React from 'react'
|
|
import { getTranslation } from '@payloadcms/translations'
|
|
|
|
export default async function MyServerComponent({ i18n }) {
|
|
const translatedTitle = getTranslation(myTranslation, i18n) // highlight-line
|
|
|
|
return (
|
|
<p>{translatedTitle}</p>
|
|
)
|
|
}
|
|
```
|
|
|
|
The best way to do this within a Client Component is to import the `useTranslation` hook from `@payloadcms/ui`:
|
|
|
|
```tsx
|
|
import React from 'react'
|
|
import { useTranslation } from '@payloadcms/ui'
|
|
|
|
export const MyClientComponent: React.FC = () => {
|
|
const { t, i18n } = useTranslation() // highlight-line
|
|
|
|
return (
|
|
<ul>
|
|
<li>{t('namespace1:key', { variable: 'value' })}</li>
|
|
<li>{t('namespace2:key', { variable: 'value' })}</li>
|
|
<li>{i18n.language}</li>
|
|
</ul>
|
|
)
|
|
}
|
|
```
|
|
|
|
<Banner type="success">
|
|
See the [Hooks](./hooks) documentation for a full list of available hooks.
|
|
</Banner>
|
|
|
|
### Getting the Current Locale
|
|
|
|
All [Custom Views](./views) can support multiple locales to be consistent with Payload's [Localization](../configuration/localization). They automatically receive the `locale` object as a prop by default. This can be used to scope API requests, etc.:
|
|
|
|
```tsx
|
|
import React from 'react'
|
|
|
|
export default async function MyServerComponent({ payload, locale }) {
|
|
const localizedPage = await payload.findByID({
|
|
collection: 'pages',
|
|
id: '123',
|
|
locale,
|
|
})
|
|
|
|
return (
|
|
<p>{localizedPage.title}</p>
|
|
)
|
|
}
|
|
```
|
|
|
|
The best way to do this within a Client Component is to import the `useLocale` hook from `@payloadcms/ui`:
|
|
|
|
```tsx
|
|
import React from 'react'
|
|
import { useLocale } from '@payloadcms/ui'
|
|
|
|
const Greeting: React.FC = () => {
|
|
const locale = useLocale() // highlight-line
|
|
|
|
const trans = {
|
|
en: 'Hello',
|
|
es: 'Hola',
|
|
}
|
|
|
|
return (
|
|
<span>{trans[locale.code]}</span>
|
|
)
|
|
}
|
|
```
|
|
|
|
<Banner type="success">
|
|
See the [Hooks](./hooks) documentation for a full list of available hooks.
|
|
</Banner>
|
|
|
|
### Styling Custom Components
|
|
|
|
Payload has a robust [CSS Library](./customizing-css) that you can use to style your Custom Components similarly to Payload's built-in styling. This will ensure that your Custom Components match the existing design system, and so that they automatically adapt to any theme changes that might occur.
|
|
|
|
To apply custom styles, simply import your own `.css` or `.scss` file into your Custom Component:
|
|
|
|
```tsx
|
|
import './index.scss'
|
|
|
|
export const MyComponent: React.FC = () => {
|
|
return (
|
|
<div className="my-component">
|
|
My Custom Component
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
Then to colorize your Custom Component's background, for example, you can use the following CSS:
|
|
|
|
```scss
|
|
.my-component {
|
|
background-color: var(--theme-elevation-500);
|
|
}
|
|
```
|
|
|
|
Payload also exports its [SCSS](https://sass-lang.com) library for reuse which includes mixins, etc. To use this, simply import it as follows into your `.scss` file:
|
|
|
|
```scss
|
|
@import '~payload/scss';
|
|
|
|
.my-component {
|
|
@include mid-break {
|
|
background-color: var(--theme-elevation-900);
|
|
}
|
|
}
|
|
|
|
```
|
|
|
|
<Banner type="success">
|
|
<strong>Note:</strong>
|
|
You can also drill into Payload's own component styles, or easily apply global, app-wide CSS. More on that [here](./customizing-css).
|
|
</Banner>
|