--- 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() ``` ### `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