I'm building a form—ideally using the useActionState server-action setup—that needs to persist its values during submissions. The problem is, I can't use the defaultValue prop on the element, because I need it to be a controlled component so I can update it dynamically on the client side.
My current setup
I tried making the controlled using local state, but then the server state doesn't apply correctly after submission. It resets the select value to be the first option, even though the server state and local client state both have most up to date values. Upon submitting again, it then submits that first incorrect select option ("Mini").
What can I do in this situation? Move to javascript query selectors?
I left a very simple example of my problem below.
Form:
"use client";
import { useActionState, useEffect, useState } from "react";
import { saveFormData } from "./actions";
type FormState = {
group: string;
};
const defaultState: FormState = {
group: "Junior",
};
export default function FormExample() {
const [state, formAction] = useActionState(saveFormData, defaultState);
const [group, setGroup] = useState(state.group);
useEffect(() => {
if (state.group !== group) {
setGroup(state.group);
}
}, [state.group]);
return (
<form action={formAction}>
<label>
Group:
<select
name="group"
value={group}
onChange={(e) => setGroup(e.target.value)}
>
<option value="Mini">Mini (11–12)</option>
<option value="Junior">Junior (12–14)</option>
<option value="Senior">Senior (15+)</option>
</select>
</label>
<button type="submit" className="btn">
Submit
</button>
</form>
);
}
Server action:
"use server";
export async function saveFormData(prevState: unknown, formData: FormData) {
const group = String(formData.get("group"));
console.log("group on server: ", group);
// Persist values here (e.g., database, cookies, etc.)
return { group };
}
1 Answer 1
This is a confirmed react 19 bug affecting the controlled <select> elements. It has introduced automatic form reset behavior that calls form.reset() after successful submissions. This creates a race condition specially with controlled <select> elements.
Here is the GitHub issue
- Form submits ->
useActionsStatebatches state update - React's automatic reset fires -> Select DOM element reset to first option
- Reconsiliation runs -> But DOM was already reset, causing desynchronization.
You can use key attribute to force remount. Also since we want to force synchronization every time state.group changes, simple approach without comparison is actually better inside the useEffect
"use client";
import { useActionState, useEffect, useState } from "react";
import { saveFormData } from "./actions";
export default function FormExample() {
const [state, formAction] = useActionState(saveFormData, { group: "Junior" });
const [group, setGroup] = useState(state.group);
useEffect(() => {
setGroup(state.group); // Always sync, no comparison needed
}, [state.group]);
return (
<form action={formAction}>
<label>
Group:
<select
key={`${state.group}-${Date.now()}`} // Force remount on each server update
name="group"
value={group}
onChange={(e) => setGroup(e.target.value)}
>
<option value="Mini">Mini (11–12)</option>
<option value="Junior">Junior (12–14)</option>
<option value="Senior">Senior (15+)</option>
</select>
</label>
<button type="submit" className="btn">
Submit
</button>
</form>
);
}
Comments
Explore related questions
See similar questions with these tags.