Skip to content

fix(ui): reflect data-dependent field access changes without save/reload#17328

Open
Code-With-Farhan wants to merge 1 commit into
payloadcms:mainfrom
Code-With-Farhan:fix/conditional-field-access-reactivity
Open

fix(ui): reflect data-dependent field access changes without save/reload#17328
Code-With-Farhan wants to merge 1 commit into
payloadcms:mainfrom
Code-With-Farhan:fix/conditional-field-access-reactivity

Conversation

@Code-With-Farhan

Copy link
Copy Markdown

What?

A field with a data-dependent access.update function (e.g. one that reads a sibling field's value, like ({ data }) => data?.title === 'test') didn't update its disabled/editable state as the user typed. It only reflected the correct state after saving and reloading the document.

Why?

Fixes #12794

Three compounding bugs, all in the debounced onChange -> form-state round trip:

  1. docPermissions was only refetched after a save completed (Edit/index.tsx's save handler), never during the debounced onChange flow. Every field-change request kept reusing permissions computed from the last-saved data.
  2. Even with fresh permissions, renderField only re-renders a field when renderAllFields is true or its lastRenderedPath doesn't match the current path - it never accounted for a field's resolved readOnly state changing between requests, so an already-rendered field kept its stale disabled prop.
  3. Even with a fresh render, Lexical's readOnly mode is only applied at initialization. LexicalProvider only remounts when its key changes, which previously only accounted for initialValue changes, not readOnly.

How?

  1. useGetDocPermissions now returns its result instead of only setting state. Edit/index.tsx's onChange handler calls it with the live, unsaved form data on every change and feeds the fresh result into the same getFormState call - mirroring the existing getDocPreferences pattern that's already called fresh on every onChange.
  2. renderField now stores the last-rendered readOnly value on FieldState (new optional property) and forces a re-render when the newly computed readOnly differs from it, in addition to the existing renderAllFields/lastRenderedPath conditions.
  3. richtext-lexical's Field.tsx now includes disabled in the LexicalProvider's remount key, alongside the existing path/rerenderProviderKey.

Added a regression test in test/form-state that reproduces the full onChange cycle (via buildFormState + the real docAccessOperation, not a hand-built permissions object) and asserts both the resolved readOnly value and that the field's custom components are re-rendered under renderAllFields: false.

Verification

  • Full monorepo build (pnpm build:core): 46/46 packages pass
  • tsc --noEmit on every touched package: 0 errors
  • pnpm test:int form-state field-access-context lexical: 230/230 passing (including the new regression test)
  • Manually verified end-to-end in the admin UI: editing a sibling field now immediately unlocks/locks a richText field with a data-dependent access.update, with no save or reload required, in both directions

Before: field stays locked after typing "test" into the sibling field, until save + reload.
Screenshot 2026-07-15 092139
After: typing directly into the field works immediately, with the Save button showing unsaved changes - proving no save/reload occurred.
Screenshot 2026-07-15 092203

@Code-With-Farhan
Code-With-Farhan force-pushed the fix/conditional-field-access-reactivity branch from ba66db7 to e70f502 Compare July 15, 2026 04:01
Fixes payloadcms#12794

A field with a data-dependent `access.update` function (e.g. one that
reads a sibling field's value) did not update its disabled/editable
state as the user typed - it only reflected the correct state after
saving and reloading the document. Three compounding bugs caused this:

1. `field.access.update` was never evaluated against the live, unsaved
   form data during the debounced onChange flow - only `field.access.read`
   was (in `addFieldStatePromise.ts`). Field-level update permissions
   came solely from `docPermissions`, which is only refetched after a
   save completes, so an in-progress edit's effect on a sibling field's
   access was invisible until save + reload.

2. Even once permissions.update correctly reflected the live data,
   `renderField` only re-renders a field when `renderAllFields` is true
   or its `lastRenderedPath` doesn't match the current path - it never
   accounted for a field's resolved `readOnly` state changing between
   requests, so an already-rendered field kept its stale disabled prop.

3. Even with a fresh render, Lexical's readOnly mode is only applied at
   initialization. `LexicalProvider` only remounts when its `key`
   changes, which previously only accounted for `initialValue` changes,
   not `readOnly`.

Fixed by:
1. Evaluating `field.access.update` in `addFieldStatePromise.ts`
   against the live request data, mirroring the existing `access.read`
   check right above it. This runs inside the buildFormState request
   that already fires on every debounced onChange, so it adds no extra
   network request - unlike an earlier version of this fix that called
   the `/access/:id` endpoint from the client on every onChange, which
   broke an existing test asserting zero extra permission-check
   requests during typing (permissions should only be refetched after
   an explicit save, per `autosave - should not fetch new doc
   permissions on every autosave`).
2. Storing the last-rendered `readOnly` value on `FieldState` (new
   optional property) and forcing `renderField` to re-render when the
   newly computed `readOnly` differs from it.
3. Including `disabled` in `LexicalProvider`'s remount `key`, alongside
   the existing `path`/`rerenderProviderKey`.

Added a regression test in test/form-state that reproduces the full
onChange cycle (via `buildFormState` + the real `docAccessOperation`)
and asserts both the resolved `readOnly` value and that the field's
custom components are re-rendered under `renderAllFields: false`.

Verified manually end-to-end: editing a sibling field now immediately
unlocks/locks a richText field with a data-dependent `access.update`,
with no save or reload required, in both directions.
@Code-With-Farhan
Code-With-Farhan force-pushed the fix/conditional-field-access-reactivity branch from e70f502 to 2fdde95 Compare July 16, 2026 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Conditionally disabling fields do not immediately update disability status

1 participant