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.
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 };
}
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
useActionsState
batches state updateYou 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>
);
}