obsidian/wiki/payloadcms/react-hooks.md
2026-05-15 15:13:56 +01:00

297 lines
8 KiB
Markdown

---
title: "Payload CMS React Hooks"
aliases: [payload-react-hooks, payloadcms-hooks, useField, useForm, useDocumentInfo]
tags: [payloadcms, react, hooks, custom-components, admin-panel]
sources: [raw/admin__react-hooks.md]
created: 2026-05-15
updated: 2026-05-15
---
# Payload CMS React Hooks
All hooks come from `@payloadcms/ui`. **Only available in client components** — add `'use client'` directive. Default [[wiki/payloadcms/custom-components|Custom Components]] are React Server Components; hooks require opting into client mode.
---
## Form / Field Hooks
### `useField`
Core hook for custom field components — reads/writes a single field's value in the parent form.
```tsx
'use client'
import { useField } from '@payloadcms/ui'
const { value, setValue } = useField({ path: 'myField' })
```
**Arguments:**
| Prop | Description |
|------|-------------|
| `path` | Path to the field in form data (falls back to `name`) |
| `validate` | Client-side validation before server submit |
| `disableFormData` | Exclude field from form submission |
| `hasRows` | Mark as row-based (array/blocks) |
**Returns:** `{ value, setValue, initialValue, errorMessage, errorPaths, readOnly, formProcessing, formSubmitted, showError, valid, rows, permissions, ... }`
#### Lexical Rich Text Gotcha
`setValue` updates form data **but the editor UI will not re-render**. To visually update a Lexical field, use `dispatchFields` with `UPDATE` action setting both `value` AND `initialValue`:
```tsx
dispatchFields({
type: 'UPDATE',
path: 'myRichTextField',
value: newValue,
initialValue: newValue, // required to trigger Lexical re-mount
})
```
---
### `useFormFields`
Retrieve **specific fields** from form state with a selector — performant, only re-renders when selected fields change (uses `use-context-selector`).
```tsx
'use client'
import { useFormFields } from '@payloadcms/ui'
const amount = useFormFields(([fields]) => fields.amount)
const fee = useFormFields(([fields]) => fields.feePercentage)
```
---
### `useAllFormFields`
Retrieve **all fields** + `dispatchFields`. Re-renders on **any** field change — use sparingly.
```tsx
'use client'
import { useAllFormFields } from '@payloadcms/ui'
import { reduceFieldsToValues, getSiblingData } from 'payload/shared'
const [fields, dispatchFields] = useAllFormFields()
const formData = reduceFieldsToValues(fields, true)
const sibling = getSiblingData(fields, 'someField')
```
**`dispatchFields` actions:**
| Action | Use case |
|--------|----------|
| `ADD_ROW` | Add row to array/blocks |
| `DUPLICATE_ROW` | Duplicate row |
| `MODIFY_CONDITION` | Toggle conditional logic |
| `MOVE_ROW` | Reorder rows |
| `REMOVE` | Remove field from state |
| `REMOVE_ROW` | Remove row from array/blocks |
| `REPLACE_STATE` | Full form state replacement |
| `UPDATE` | Update any field property |
---
### `useForm`
Interact with the form itself (actions triggered by user events). **Do not rely on `fields` for up-to-date values** — it's deprecated/stale. Use only for action-based callbacks.
Key methods: `submit`, `validateForm`, `createFormData`, `getData`, `getField`, `getFields`, `getSiblingData`, `getDataByPath`, `reset`, `addFieldRow`, `removeFieldRow`, `replaceFieldRow`, `setModified`, `setProcessing`, `setSubmitted`
```tsx
const { addFieldRow } = useForm()
addFieldRow({
path: 'arrayField',
schemaPath: 'arrayField',
rowIndex: 0,
subFieldState: { textField: { initialValue: 'text', valid: true, value: 'text' } },
// blockType: 'slug' // required for block arrays
})
```
---
### `useDocumentForm`
Same as `useForm` but always targets the **top-level document form** — useful inside Lexical blocks or other child forms that create their own `Form` context.
---
## Document / Collection Hooks
### `useDocumentInfo`
Comprehensive info about the document being edited.
Key properties: `id`, `collectionSlug`, `globalSlug`, `docPermissions`, `isEditing`, `isLocked`, `documentIsLocked`, `hasPublishedDoc`, `hasPublishPermission`, `hasSavePermission`, `initialData`, `data`, `versionCount`, `currentEditor`, `apiURL`, `uploadStatus`
Key methods: `getDocPermissions`, `getDocPreferences`, `setDocFieldPreferences`, `unlockDocument`, `updateDocumentEditor`, `updateSavedDocumentData`
```tsx
const { id } = useDocumentInfo()
// id is undefined on create form
```
---
### `useDocumentTitle`
Returns document title without triggering re-renders on other document state changes.
```tsx
const { title, setDocumentTitle } = useDocumentTitle()
```
---
### `useDocumentEvents`
Subscribe to cross-document events (e.g., nested doc updated in a drawer).
```tsx
const { mostRecentUpdate, reportUpdate } = useDocumentEvents()
// mostRecentUpdate: { entitySlug, id?, updatedAt }
```
---
## List View Hooks
### `useListQuery`
Subscribe to list view data and query state.
```tsx
const { data, query } = useListQuery()
```
Key properties: `data`, `query`, `defaultLimit`, `defaultSort`, `modified`, `handlePageChange`, `handlePerPageChange`, `handleSearchChange`, `handleSortChange`, `handleWhereChange`
---
### `useSelection`
Access/control row selection in list view.
```tsx
const { count, toggleAll, totalDocs, selected, setSelection, getQueryParams, selectAll } = useSelection()
// selectAll enum: 'allAvailable' | 'allInPage' | 'none' | 'some'
```
---
### `useTableColumns`
Manage list view column visibility and order.
```tsx
const { columns, setActiveColumns, toggleColumn, moveColumn, resetColumnsState } = useTableColumns()
setActiveColumns(['id', 'createdAt']) // activates specific cols, preserves order
```
---
## UI / Navigation Hooks
### `useCollapsible`
Control nearest collapsible: `{ isCollapsed, isVisible, toggle, isWithinCollapsible }`
### `useStepNav`
Set breadcrumb nav in app header.
```tsx
const { setStepNav } = useStepNav()
// StepNavItem: { label: string, url?: string }
```
### `useEditDepth`
How many modal/drawer nesting levels deep the component is.
### `useRouteTransition`
Wrap `router.push` to show visual transition feedback.
```tsx
const { startRouteTransition } = useRouteTransition()
startRouteTransition(() => router.push('/somewhere'))
```
---
## Auth / Config / Preferences / Theme
### `useAuth`
```tsx
const { user, token, logOut, refreshCookie, setToken, refreshPermissions, permissions } = useAuth<User>()
```
### `useConfig`
```tsx
const { config, getEntityConfig } = useConfig()
const mediaConfig = getEntityConfig({ collectionSlug: 'media' })
```
### `useLocale`
```tsx
const locale = useLocale() // { code, label, rtl }
```
### `useTheme`
```tsx
const { theme, setTheme, autoMode } = useTheme()
// theme: 'light' | 'dark' | 'auto'
```
### `usePreferences`
Get/set persistent user preferences. See [[wiki/payloadcms/admin-preferences|Admin Preferences]] for full docs.
---
## `usePayloadAPI`
Reactive REST API requests to Payload.
```tsx
const [{ data, isError, isLoading }, { setParams }] = usePayloadAPI('/api/posts/123', {
initialParams: { depth: 1 },
})
setParams({ cacheBust: Date.now() }) // trigger refetch
```
---
## Key Takeaways
- All hooks require `'use client'` — they cannot run in Server Components
- `useFormFields` > `useAllFormFields` for performance — only subscribes to specific fields
- **Lexical rich text**: `setValue` updates data but not the UI — use `dispatchFields` with both `value` + `initialValue`
- `useForm` fields property is deprecated/stale — use it only for action callbacks, not reactive state
- `useDocumentForm` vs `useForm`: use the former when inside nested forms (Lexical blocks)
- `dispatchFields` `UPDATE` action is the low-level escape hatch for any field state mutation
- `useDocumentTitle` is split from `useDocumentInfo` to prevent unnecessary re-renders
---
## Related
- [[wiki/payloadcms/custom-components|Custom Components]] — where hooks are used
- [[wiki/payloadcms/admin-preferences|Admin Preferences]] — `usePreferences` deep dive
- [[wiki/payloadcms/admin-panel-overview|Admin Panel Overview]] — full admin config
---
## Sources
- `raw/admin__react-hooks.md`
- Official docs: https://payloadcms.com/docs/admin/react-hooks