---
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
