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>
950 lines
29 KiB
Plaintext
950 lines
29 KiB
Plaintext
---
|
|
title: React Hooks
|
|
label: React Hooks
|
|
order: 70
|
|
desc: Make use of all of the powerful React hooks that Payload provides.
|
|
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
|
---
|
|
|
|
Payload provides a variety of powerful [React Hooks](https://react.dev/reference/react-dom/hooks) that can be used within your own [Custom Components](./components), such as [Custom Fields](./fields). With them, you can interface with Payload itself to build just about any type of complex customization you can think of.
|
|
|
|
<Banner type="warning">
|
|
<strong>Reminder:</strong>
|
|
All Custom Components are [React Server Components](https://react.dev/reference/rsc/server-components) by default. Hooks, on the other hand, are only available in client-side environments. To use hooks, [ensure your component is a client component](./components#client-components).
|
|
</Banner>
|
|
|
|
## useField
|
|
|
|
The `useField` hook is used internally within all field components. It manages sending and receiving a field's state from its parent form. When you build a [Custom Field Component](./fields), you will be responsible for sending and receiving the field's `value` to and from the form yourself.
|
|
|
|
To do so, import the `useField` hook as follows:
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useField } from '@payloadcms/ui'
|
|
|
|
const CustomTextField: React.FC = () => {
|
|
const { value, setValue, path } = useField() // highlight-line
|
|
|
|
return (
|
|
<div>
|
|
<p>
|
|
{path}
|
|
</p>
|
|
<input
|
|
onChange={(e) => { setValue(e.target.value) }}
|
|
value={value}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
The `useField` hook accepts the following arguments:
|
|
|
|
| Property | Description |
|
|
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `path` | If you do not provide a `path` or a `name`, this hook will look for one using the [`useFieldProps`](#usefieldprops) hook. |
|
|
| `validate` | A validation function executed client-side _before_ submitting the form to the server. Different than [Field-level Validation](../fields/overview#validation) which runs strictly on the server. |
|
|
| `disableFormData` | If `true`, the field will not be included in the form data when the form is submitted. |
|
|
| `hasRows` | If `true`, the field will be treated as a field with rows. This is useful for fields like `array` and `blocks`. |
|
|
|
|
The `useField` hook returns the following object:
|
|
|
|
```ts
|
|
type FieldType<T> = {
|
|
errorMessage?: string
|
|
errorPaths?: string[]
|
|
filterOptions?: FilterOptionsResult
|
|
formInitializing: boolean
|
|
formProcessing: boolean
|
|
formSubmitted: boolean
|
|
initialValue?: T
|
|
path: string
|
|
permissions: FieldPermissions
|
|
readOnly?: boolean
|
|
rows?: Row[]
|
|
schemaPath: string
|
|
setValue: (val: unknown, disableModifyingForm?: boolean) => void
|
|
showError: boolean
|
|
valid?: boolean
|
|
value: T
|
|
}
|
|
```
|
|
|
|
## useFieldProps
|
|
|
|
All [Custom Field Components](./fields#the-field-component) are rendered on the server, and as such, only have access to static props at render time. But, some fields can be dynamic, such as when nested in an [`array`](../fields/array) or [`blocks`](../fields/block) field. For example, items can be added, re-ordered, or deleted on-the-fly.
|
|
|
|
For this reason, dynamic props like `path` are managed in their own React context, which can be accessed using the `useFieldProps` hook:
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useFieldProps } from '@payloadcms/ui'
|
|
|
|
const CustomTextField: React.FC = () => {
|
|
const { path } = useFieldProps() // highlight-line
|
|
|
|
return (
|
|
<div>
|
|
{path}
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
<Banner type="success">
|
|
<strong>Tip:</strong>
|
|
The [`useField`](#usefield) hook calls the `useFieldProps` hook internally, so you don't need to use both in the same component unless explicitly needed.
|
|
</Banner>
|
|
|
|
## useFormFields
|
|
|
|
There are times when a custom field component needs to have access to data from other fields, and you have a few options to do so. The `useFormFields` hook is a powerful and highly performant way to retrieve a form's field state, as well as to retrieve the `dispatchFields` method, which can be helpful for setting other fields' form states from anywhere within a form.
|
|
|
|
<Banner type="success">
|
|
<strong>This hook is great for retrieving only certain fields from form state</strong> because it
|
|
ensures that it will only cause a rerender when the items that you ask for change.
|
|
</Banner>
|
|
|
|
Thanks to the awesome package [`use-context-selector`](https://github.com/dai-shi/use-context-selector), you can retrieve a specific field's state easily. This is ideal because you can ensure you have an up-to-date field state, and your component will only re-render when _that field's state_ changes.
|
|
|
|
You can pass a Redux-like selector into the hook, which will ensure that you retrieve only the field that you want. The selector takes an argument with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
|
|
|
|
```tsx
|
|
'use client'
|
|
import type { useFormFields } from '@payloadcms/ui'
|
|
|
|
const MyComponent: React.FC = () => {
|
|
// Get only the `amount` field state, and only cause a rerender when that field changes
|
|
const amount = useFormFields(([fields, dispatch]) => fields.amount)
|
|
|
|
// Do the same thing as above, but to the `feePercentage` field
|
|
const feePercentage = useFormFields(([fields, dispatch]) => fields.feePercentage)
|
|
|
|
if (typeof amount?.value !== 'undefined' && typeof feePercentage?.value !== 'undefined') {
|
|
return <span>The fee is ${(amount.value * feePercentage.value) / 100}</span>
|
|
}
|
|
}
|
|
```
|
|
|
|
## useAllFormFields
|
|
|
|
**To retrieve more than one field**, you can use the `useAllFormFields` hook. Your component will re-render when _any_ field changes, so use this hook only if you absolutely need to. Unlike the `useFormFields` hook, this hook does not accept a "selector", and it always returns an array with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
|
|
|
|
You can do lots of powerful stuff by retrieving the full form state, like using built-in helper functions to reduce field state to values only, or to retrieve sibling data by path.
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useAllFormFields } from '@payloadcms/ui'
|
|
import { reduceFieldsToValues, getSiblingData } from 'payload/shared'
|
|
|
|
const ExampleComponent: React.FC = () => {
|
|
// the `fields` const will be equal to all fields' state,
|
|
// and the `dispatchFields` method is usable to send field state up to the form
|
|
const [fields, dispatchFields] = useAllFormFields();
|
|
|
|
// Pass in fields, and indicate if you'd like to "unflatten" field data.
|
|
// The result below will reflect the data stored in the form at the given time
|
|
const formData = reduceFieldsToValues(fields, true);
|
|
|
|
// Pass in field state and a path,
|
|
// and you will be sent all sibling data of the path that you've specified
|
|
const siblingData = getSiblingData(fields, 'someFieldName');
|
|
|
|
return (
|
|
// return some JSX here if necessary
|
|
)
|
|
};
|
|
```
|
|
|
|
#### Updating other fields' values
|
|
|
|
If you are building a Custom Component, then you should use `setValue` which is returned from the `useField` hook to programmatically set your field's value. But if you're looking to update _another_ field's value, you can use `dispatchFields` returned from `useFormFields`.
|
|
|
|
You can send the following actions to the `dispatchFields` function.
|
|
|
|
| Action | Description |
|
|
| ---------------------- | -------------------------------------------------------------------------- |
|
|
| **`ADD_ROW`** | Adds a row of data (useful in array / block field data) |
|
|
| **`DUPLICATE_ROW`** | Duplicates a row of data (useful in array / block field data) |
|
|
| **`MODIFY_CONDITION`** | Updates a field's conditional logic result (true / false) |
|
|
| **`MOVE_ROW`** | Moves a row of data (useful in array / block field data) |
|
|
| **`REMOVE`** | Removes a field from form state |
|
|
| **`REMOVE_ROW`** | Removes a row of data from form state (useful in array / block field data) |
|
|
| **`REPLACE_STATE`** | Completely replaces form state |
|
|
| **`UPDATE`** | Update any property of a specific field's state |
|
|
|
|
To see types for each action supported within the `dispatchFields` hook, check out the Form types [here](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/components/forms/Form/types.ts).
|
|
|
|
## useForm
|
|
|
|
The `useForm` hook can be used to interact with the form itself, and sends back many methods that can be used to reactively fetch form state without causing rerenders within your components each time a field is changed. This is useful if you have action-based callbacks that your components fire, and need to interact with form state _based on a user action_.
|
|
|
|
<Banner type="warning">
|
|
<strong>Warning:</strong>
|
|
<br />
|
|
This hook is optimized to avoid causing rerenders when fields change, and as such, its `fields`
|
|
property will be out of date. You should only leverage this hook if you need to perform actions
|
|
against the form in response to your users' actions. Do not rely on its returned "fields" as being
|
|
up-to-date. They will be removed from this hook's response in an upcoming version.
|
|
</Banner>
|
|
|
|
The `useForm` hook returns an object with the following properties:
|
|
|
|
<TableWithDrawers
|
|
columns={[
|
|
'Action',
|
|
'Description',
|
|
'Example',
|
|
]}
|
|
rows={[
|
|
[
|
|
{
|
|
value: <strong><code>fields</code></strong>,
|
|
},
|
|
{
|
|
value: "Deprecated. This property cannot be relied on as up-to-date.",
|
|
},
|
|
{
|
|
value: ''
|
|
}
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>submit</code></strong>,
|
|
},
|
|
{
|
|
value: "Method to trigger the form to submit",
|
|
},
|
|
{
|
|
value: ''
|
|
}
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>dispatchFields</code></strong>,
|
|
},
|
|
{
|
|
value: "Dispatch actions to the form field state",
|
|
},
|
|
{
|
|
value: ''
|
|
}
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>validateForm</code></strong>,
|
|
},
|
|
{
|
|
value: "Trigger a validation of the form state",
|
|
},
|
|
{
|
|
value: ''
|
|
}
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>createFormData</code></strong>,
|
|
},
|
|
{
|
|
value: <>Create a <code>multipart/form-data</code> object from the current form's state</>,
|
|
},
|
|
{
|
|
value: ''
|
|
}
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>disabled</code></strong>,
|
|
},
|
|
{
|
|
value: "Boolean denoting whether or not the form is disabled",
|
|
},
|
|
{
|
|
value: ''
|
|
}
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>getFields</code></strong>,
|
|
},
|
|
{
|
|
value: 'Gets all fields from state',
|
|
},
|
|
{
|
|
value: '',
|
|
}
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>getField</code></strong>,
|
|
},
|
|
{
|
|
value: 'Gets a single field from state by path',
|
|
},
|
|
{
|
|
value: '',
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>getData</code></strong>,
|
|
},
|
|
{
|
|
value: 'Returns the data stored in the form',
|
|
},
|
|
{
|
|
value: '',
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>getSiblingData</code></strong>,
|
|
},
|
|
{
|
|
value: 'Returns form sibling data for the given field path',
|
|
},
|
|
{
|
|
value: '',
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>setModified</code></strong>,
|
|
},
|
|
{
|
|
value: <>Set the form\'s <code>modified</code> state</>,
|
|
},
|
|
{
|
|
value: '',
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>setProcessing</code></strong>,
|
|
},
|
|
{
|
|
value: <>Set the form\'s <code>processing</code> state</>,
|
|
},
|
|
{
|
|
value: '',
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>setSubmitted</code></strong>,
|
|
},
|
|
{
|
|
value: <>Set the form\'s <code>submitted</code> state</>,
|
|
},
|
|
{
|
|
value: '',
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>formRef</code></strong>,
|
|
},
|
|
{
|
|
value: 'The ref from the form HTML element',
|
|
},
|
|
{
|
|
value: '',
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>reset</code></strong>,
|
|
},
|
|
{
|
|
value: 'Method to reset the form to its initial state',
|
|
},
|
|
{
|
|
value: '',
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>addFieldRow</code></strong>,
|
|
},
|
|
{
|
|
value: "Method to add a row on an array or block field",
|
|
},
|
|
{
|
|
drawerTitle: 'addFieldRow',
|
|
drawerDescription: 'A useful method to programmatically add a row to an array or block field.',
|
|
drawerSlug: 'addFieldRow',
|
|
drawerContent: (
|
|
<>
|
|
<TableWithDrawers
|
|
columns={[
|
|
'Prop',
|
|
'Description',
|
|
]}
|
|
rows={[
|
|
[
|
|
{
|
|
value: <strong><code>path</code></strong>,
|
|
},
|
|
{
|
|
value: "The path to the array or block field",
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>rowIndex</code></strong>,
|
|
},
|
|
{
|
|
value: "The index of the row to add. If omitted, the row will be added to the end of the array.",
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>data</code></strong>,
|
|
},
|
|
{
|
|
value: "The data to add to the row",
|
|
},
|
|
],
|
|
]}
|
|
/>
|
|
|
|
{' '}
|
|
|
|
<br />
|
|
|
|
{' '}
|
|
|
|
<pre>
|
|
{`import { useForm } from "payload/components/forms";
|
|
|
|
export const CustomArrayManager = () => {
|
|
const { addFieldRow } = useForm()
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
addFieldRow({
|
|
path: "arrayField",
|
|
rowIndex: 0,
|
|
data: {
|
|
textField: "text",
|
|
// blockType: "yourBlockSlug",
|
|
// ^ if managing a block array, you need to specify the block type
|
|
},
|
|
})
|
|
}}
|
|
>
|
|
Add Row
|
|
</button>
|
|
)
|
|
}`}
|
|
</pre>
|
|
|
|
<p>An example config to go along with the Custom Component</p>
|
|
<pre>
|
|
{`const ExampleCollection = {
|
|
slug: "example-collection",
|
|
fields: [
|
|
{
|
|
name: "arrayField",
|
|
type: "array",
|
|
fields: [
|
|
{
|
|
name: "textField",
|
|
type: "text",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "ui",
|
|
name: "customArrayManager",
|
|
admin: {
|
|
components: {
|
|
Field: '/path/to/CustomArrayManagerField',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
}`}
|
|
</pre>
|
|
</>
|
|
)
|
|
}
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>removeFieldRow</code></strong>,
|
|
},
|
|
{
|
|
value: "Method to remove a row from an array or block field",
|
|
},
|
|
{
|
|
drawerTitle: 'removeFieldRow',
|
|
drawerDescription: 'A useful method to programmatically remove a row from an array or block field.',
|
|
drawerSlug: 'removeFieldRow',
|
|
drawerContent: (
|
|
<>
|
|
<TableWithDrawers
|
|
columns={[
|
|
'Prop',
|
|
'Description',
|
|
]}
|
|
rows={[
|
|
[
|
|
{
|
|
value: <strong><code>path</code></strong>,
|
|
},
|
|
{
|
|
value: "The path to the array or block field",
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>rowIndex</code></strong>,
|
|
},
|
|
{
|
|
value: "The index of the row to remove",
|
|
},
|
|
],
|
|
]}
|
|
/>
|
|
|
|
{' '}
|
|
|
|
<br />
|
|
|
|
{' '}
|
|
|
|
<pre>
|
|
{`import { useForm } from "payload/components/forms";
|
|
|
|
export const CustomArrayManager = () => {
|
|
const { removeFieldRow } = useForm()
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
removeFieldRow({
|
|
path: "arrayField",
|
|
rowIndex: 0,
|
|
})
|
|
}}
|
|
>
|
|
Remove Row
|
|
</button>
|
|
)
|
|
}`}
|
|
</pre>
|
|
|
|
<p>An example config to go along with the Custom Component</p>
|
|
<pre>
|
|
{`const ExampleCollection = {
|
|
slug: "example-collection",
|
|
fields: [
|
|
{
|
|
name: "arrayField",
|
|
type: "array",
|
|
fields: [
|
|
{
|
|
name: "textField",
|
|
type: "text",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "ui",
|
|
name: "customArrayManager",
|
|
admin: {
|
|
components: {
|
|
Field: '/path/to/CustomArrayManagerField',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
}`}
|
|
</pre>
|
|
</>
|
|
)
|
|
}
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>replaceFieldRow</code></strong>,
|
|
},
|
|
{
|
|
value: "Method to replace a row from an array or block field",
|
|
},
|
|
{
|
|
drawerTitle: 'replaceFieldRow',
|
|
drawerDescription: 'A useful method to programmatically replace a row from an array or block field.',
|
|
drawerSlug: 'replaceFieldRow',
|
|
drawerContent: (
|
|
<>
|
|
<TableWithDrawers
|
|
columns={[
|
|
'Prop',
|
|
'Description',
|
|
]}
|
|
rows={[
|
|
[
|
|
{
|
|
value: <strong><code>path</code></strong>,
|
|
},
|
|
{
|
|
value: "The path to the array or block field",
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>rowIndex</code></strong>,
|
|
},
|
|
{
|
|
value: "The index of the row to replace",
|
|
},
|
|
],
|
|
[
|
|
{
|
|
value: <strong><code>data</code></strong>,
|
|
},
|
|
{
|
|
value: "The data to replace within the row",
|
|
},
|
|
],
|
|
]}
|
|
/>
|
|
|
|
{' '}
|
|
|
|
<br />
|
|
|
|
{' '}
|
|
|
|
<pre>
|
|
{`import { useForm } from "payload/components/forms";
|
|
|
|
export const CustomArrayManager = () => {
|
|
const { replaceFieldRow } = useForm()
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
replaceFieldRow({
|
|
path: "arrayField",
|
|
rowIndex: 0,
|
|
data: {
|
|
textField: "updated text",
|
|
// blockType: "yourBlockSlug",
|
|
// ^ if managing a block array, you need to specify the block type
|
|
},
|
|
})
|
|
}}
|
|
>
|
|
Replace Row
|
|
</button>
|
|
)
|
|
}`}
|
|
</pre>
|
|
|
|
<p>An example config to go along with the Custom Component</p>
|
|
<pre>
|
|
{`const ExampleCollection = {
|
|
slug: "example-collection",
|
|
fields: [
|
|
{
|
|
name: "arrayField",
|
|
type: "array",
|
|
fields: [
|
|
{
|
|
name: "textField",
|
|
type: "text",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: "ui",
|
|
name: "customArrayManager",
|
|
admin: {
|
|
components: {
|
|
Field: '/path/to/CustomArrayManagerField',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
}`}
|
|
</pre>
|
|
</>
|
|
)
|
|
}
|
|
],
|
|
]}
|
|
/>
|
|
|
|
## useCollapsible
|
|
|
|
The `useCollapsible` hook allows you to control parent collapsibles:
|
|
|
|
| Property | Description |
|
|
| ------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
| **`isCollapsed`** | State of the collapsible. `true` if open, `false` if collapsed. |
|
|
| **`isVisible`** | If nested, determine if the nearest collapsible is visible. `true` if no parent is closed, `false` otherwise. |
|
|
| **`toggle`** | Toggles the state of the nearest collapsible. |
|
|
| **`isWithinCollapsible`** | Determine when you are within another collapsible. |
|
|
|
|
**Example:**
|
|
|
|
```tsx
|
|
'use client'
|
|
import React from 'react'
|
|
|
|
import { useCollapsible } from '@payloadcms/ui'
|
|
|
|
const CustomComponent: React.FC = () => {
|
|
const { isCollapsed, toggle } = useCollapsible()
|
|
|
|
return (
|
|
<div>
|
|
<p className="field-type">I am {isCollapsed ? 'closed' : 'open'}</p>
|
|
<button onClick={toggle} type="button">
|
|
Toggle
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
## useDocumentInfo
|
|
|
|
The `useDocumentInfo` hook provides lots of information about the document currently being edited, including the following:
|
|
|
|
| Property | Description |
|
|
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
| **`collection`** | If the doc is a collection, its Collection Config will be returned |
|
|
| **`global`** | If the doc is a global, its Global Config will be returned |
|
|
| **`id`** | If the doc is a collection, its ID will be returned |
|
|
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences |
|
|
| **`versions`** | Versions of the current doc |
|
|
| **`unpublishedVersions`** | Unpublished versions of the current doc |
|
|
| **`publishedDoc`** | The currently published version of the doc being edited |
|
|
| **`getVersions`** | Method to trigger the retrieval of document versions |
|
|
| **`docPermissions`** | The current documents permissions. Collection document permissions fallback when no id is present (i.e. on create) |
|
|
| **`getDocPermissions`** | Method to trigger the retrieval of document level permissions |
|
|
|
|
**Example:**
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useDocumentInfo } from '@payloadcms/ui'
|
|
|
|
const LinkFromCategoryToPosts: React.FC = () => {
|
|
// highlight-start
|
|
const { id } = useDocumentInfo()
|
|
// highlight-end
|
|
|
|
// id will be undefined on the create form
|
|
if (!id) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`}>
|
|
View posts
|
|
</a>
|
|
)
|
|
}
|
|
```
|
|
|
|
## useLocale
|
|
|
|
In any Custom Component you can get the selected locale object with the `useLocale` hook. `useLocale`gives you the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example:
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useLocale } from '@payloadcms/ui'
|
|
|
|
const Greeting: React.FC = () => {
|
|
// highlight-start
|
|
const locale = useLocale()
|
|
// highlight-end
|
|
|
|
const trans = {
|
|
en: 'Hello',
|
|
es: 'Hola',
|
|
}
|
|
|
|
return <span> {trans[locale.code]} </span>
|
|
}
|
|
```
|
|
|
|
## useAuth
|
|
|
|
Useful to retrieve info about the currently logged in user as well as methods for interacting with it. It sends back an object with the following properties:
|
|
|
|
| Property | Description |
|
|
| ------------------------ | --------------------------------------------------------------------------------------- |
|
|
| **`user`** | The currently logged in user |
|
|
| **`logOut`** | A method to log out the currently logged in user |
|
|
| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token |
|
|
| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory |
|
|
| **`token`** | The logged in user's token (useful for creating preview links, etc.) |
|
|
| **`refreshPermissions`** | Load new permissions (useful when content that effects permissions has been changed) |
|
|
| **`permissions`** | The permissions of the current user |
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useAuth } from '@payloadcms/ui'
|
|
import type { User } from '../payload-types.ts'
|
|
|
|
const Greeting: React.FC = () => {
|
|
// highlight-start
|
|
const { user } = useAuth<User>()
|
|
// highlight-end
|
|
|
|
return <span>Hi, {user.email}!</span>
|
|
}
|
|
```
|
|
|
|
## useConfig
|
|
|
|
Used to easily retrieve the Payload [Client Config](./components#accessing-the-payload-config).
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useConfig } from '@payloadcms/ui'
|
|
|
|
const MyComponent: React.FC = () => {
|
|
// highlight-start
|
|
const { config } = useConfig()
|
|
// highlight-end
|
|
|
|
return <span>{config.serverURL}</span>
|
|
}
|
|
```
|
|
|
|
## useEditDepth
|
|
|
|
Sends back how many editing levels "deep" the current component is. Edit depth is relevant while adding new documents / editing documents in modal windows and other cases.
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useEditDepth } from '@payloadcms/ui'
|
|
|
|
const MyComponent: React.FC = () => {
|
|
// highlight-start
|
|
const editDepth = useEditDepth()
|
|
// highlight-end
|
|
|
|
return <span>My component is {editDepth} levels deep</span>
|
|
}
|
|
```
|
|
|
|
## usePreferences
|
|
|
|
Returns methods to set and get user preferences. More info can be found [here](https://payloadcms.com/docs/admin/preferences).
|
|
|
|
## useTheme
|
|
|
|
Returns the currently selected theme (`light`, `dark` or `auto`), a set function to update it and a boolean `autoMode`, used to determine if the theme value should be set automatically based on the user's device preferences.
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useTheme } from '@payloadcms/ui'
|
|
|
|
const MyComponent: React.FC = () => {
|
|
// highlight-start
|
|
const { autoMode, setTheme, theme } = useTheme()
|
|
// highlight-end
|
|
|
|
return (
|
|
<>
|
|
<span>
|
|
The current theme is {theme} and autoMode is {autoMode}
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))}
|
|
>
|
|
Toggle theme
|
|
</button>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
## useTableColumns
|
|
|
|
Returns methods to manipulate table columns
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useTableColumns } from '@payloadcms/ui'
|
|
|
|
const MyComponent: React.FC = () => {
|
|
// highlight-start
|
|
const { setActiveColumns } = useTableColumns()
|
|
|
|
const resetColumns = () => {
|
|
setActiveColumns(['id', 'createdAt', 'updatedAt'])
|
|
}
|
|
// highlight-end
|
|
|
|
return (
|
|
<button type="button" onClick={resetColumns}>
|
|
Reset columns
|
|
</button>
|
|
)
|
|
}
|
|
```
|
|
|
|
## useTableCell
|
|
|
|
Similar to [`useFieldProps`](#usefieldprops), all [Custom Cell Components](./fields#the-cell-component) are rendered on the server, and as such, only have access to static props at render time. But, some props need to be dynamic, such as the field value itself.
|
|
|
|
For this reason, dynamic props like `cellData` are managed in their own React context, which can be accessed using the `useTableCell` hook.
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useTableCell } from '@payloadcms/ui'
|
|
|
|
const MyComponent: React.FC = () => {
|
|
const { cellData } = useTableCell() // highlight-line
|
|
|
|
return (
|
|
<div>
|
|
{cellData}
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
## useDocumentEvents
|
|
|
|
The `useDocumentEvents` hook provides a way of subscribing to cross-document events, such as updates made to nested documents within a drawer. This hook will report document events that are outside the scope of the document currently being edited. This hook provides the following:
|
|
|
|
| Property | Description |
|
|
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| **`mostRecentUpdate`** | An object containing the most recently updated document. It contains the `entitySlug`, `id` (if collection), and `updatedAt` properties |
|
|
| **`reportUpdate`** | A method used to report updates to documents. It accepts the same arguments as the `mostRecentUpdate` property. |
|
|
|
|
**Example:**
|
|
|
|
```tsx
|
|
'use client'
|
|
import { useDocumentEvents } from '@payloadcms/ui'
|
|
|
|
const ListenForUpdates: React.FC = () => {
|
|
const { mostRecentUpdate } = useDocumentEvents()
|
|
|
|
return <span>{JSON.stringify(mostRecentUpdate)}</span>
|
|
}
|
|
```
|
|
|
|
<Banner type="info">
|
|
Right now the `useDocumentEvents` hook only tracks recently updated documents, but in the future
|
|
it will track more document-related events as needed, such as document creation, deletion, etc.
|
|
</Banner>
|