payloadcms / payloadcms/payload
Form state: a re-render of the admin page replaces everything the editor has typed (initialState effect), and the merge strips the value that would restore it
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
Anything that makes the admin's RSC tree re-render while an editor is typing silently replaces
every value in the form with the document's pristine initialState. The typed text goes, and on
an upload collection the selected file goes with it, because file.value only ever exists in
client state. There is no error and nothing in the console.
The trigger in our case was a Set-Cookie written by Next middleware on the admin path. Payload
posts form state as a Server Action to the same URL, Next answers a Server Action whose response
carries a cookie with a re-rendered tree, and @payloadcms/next's Document view therefore hands
<Form> a brand new initialState object. Any other cause of a re-render should do the same.
The chain, measured in the browser with a logging wrapper around fieldReducer on 3.89.0:
UPDATE alt: {lastRenderedPath, valid, passesCondition, value: "abcd", isModified: true}
MERGE_SERVER_STATE alt: unchanged, value "abcd" still there <- the merge is not the problem
RESPONSE 200 (next-action, our Set-Cookie on it)
REPLACE_STATE alt: {lastRenderedPath, valid, passesCondition} <- value and isModified gone
The replacement is this effect in packages/ui/src/forms/Form/index.tsx (v3.89.0, byte
identical in 3.90.1):
useEffect(() => {
if (initialState) {
contextRef.current = { ...initContextState } as FormContextType
dispatchFields({ type: 'REPLACE_STATE', optimize: false, sanitize: true, state: initialState })
}
}, [initialState, dispatchFields])
REPLACE_STATE with optimize: false returns action.state wholesale
(packages/ui/src/forms/Form/fieldReducer.ts), so local values are not merged, and resetting
contextRef to initContextState drops the file the editor picked, which is why the upload
preview disappears too (the thumbnail is gated on the form-state entry file.value, and
deepCopyObjectSimpleWithoutReactComponents means the server can never send it back).
Two things make it worse than a lost keystroke:
- The same response that triggers the re-render contains the correct value
("alt":{"lastRenderedPath":"alt","value":"abcd","initialValue":"abcd"}), but the form cannot
recover it: on the typing pathMERGE_SERVER_STATEis dispatched with noacceptValues
(Form/index.tsx), somergeServerFormStatedeliberately omitsvalueandinitialValue
from the incoming state. - Collections with autosave hide it, because autosave writes the value to the server before it
is lost. It is only visible on collections without autosave, where a required field then makes
the document impossible to save by typing.
A guard on that effect, so it applies initialState only when the form has not been modified
(or only once per document), would leave live values alone without changing what the effect is
for.
Link to the code that reproduces this issue
I do not have a public repro repository; this is a private commercial project. The steps below
reproduce it from a stock create-payload-app in under five minutes, and I am happy to build a
repro repo if that is what it takes to get this looked at.
Reproduction Steps
- A stock Payload 3.89.0 or 3.90.1 app on Next 16 with a Postgres adapter.
- An upload collection with a required text field, and no autosave:
{ slug: 'media', upload: { mimeTypes: ['image/*'] }, fields: [{ name: 'alt', type: 'text', required: true }] } - Add middleware (
proxy.tson Next 16) that writes a cookie on every request, including
/admin:export default function proxy() { const response = NextResponse.next() response.cookies.set('env', 'development', { path: '/' }) return response } export const config = { matcher: '/((?!api|_next|.*\\..*).*)' } - Sign in, open
/admin/collections/media/create, type intoalt. - About a second later (the 250ms form-state debounce plus the round trip) the input empties
itself. If a file was selected, its preview is gone too. The DOM node is the same one, so this
is not a remount.
Removing the cookie write from that one response (writing it only on GET, for instance) makes the
symptom disappear, with no REPLACE_STATE after the Server Action response.
Which area(s) are affected?
area: ui
Environment Info
payload and every @payloadcms/* : 3.89.0, and reproduced identically on 3.90.1
next : 16.3.5
react : 19.3.0
database: @payloadcms/db-postgres, Postgres 18
browser : Chromium (Playwright 1.63)
node : 24.20.0
Comparing the published @payloadcms/ui dist between 3.89.0 and 3.90.1 for forms/Form,
views/Edit and providers/DocumentInfo, the only difference is a client-upload guard in
Form/index.js, so nothing about this changed between those releases.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the reset with the stock app steps, then read the initialState effect in packages/ui/src/forms/Form/index.tsx and REPLACE_STATE in packages/ui/src/forms/Form/fieldReducer.ts. Trace the MERGE_SERVER_STATE path and contextRef reset, then verify that a server-action re-render preserves typed values, modified state, and selected upload files without autosave.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nextjs, react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100