[React 19] Controlled `<select>` component is subject to automatic form reset
The controlled component also resets the "select" after the action is triggered. but the "input" component does not.
"use client";
import { useActionState, useState } from "react";
function add() {
return Date.now();
}
export default function Page() {
const [state, formAction] = useActionState(add, 0);
const [name, setName] = useState("");
const [type, setType] = useState("2");
return (
<form action={formAction}>
<p>{state}</p>
<p>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</p>
<p>
<select
name="gender"
value={type}
onChange={(e) => setType(e.target.value)}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</p>
<button>submit</button>
</form>
);
}
Repro: https://codesandbox.io/p/sandbox/stupefied-cohen-n578l6
@cjg1122 Can you please add actual and expected behavior?
Since useActionState passes the previous state and returns the updated state, we can reset the input and select values accordingly.
Here's an updated version of the code that properly handles resetting both the input and select elements:
"use client";
import { useActionState, useState } from "react";
// This function handles the form submission
function add(previousState, formData) {
// Process the formData and return the new state
const newState = Date.now();
// Reset the input and select values
formData.reset();
return newState;
}
export default function Page() {
// useActionState hook manages the form state and action
const [state, formAction] = useActionState(add, 0);
// Local state for input and select values
const [name, setName] = useState("");
const [type, setType] = useState("2");
return (
<form
action={(e) => {
formAction(e); // Call the form action
setName(""); // Reset the input value
setType("2"); // Reset the select value
}}
>
<p>{state}</p>
<p>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</p>
<p>
<select
name="gender"
value={type}
onChange={(e) => setType(e.target.value)}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</p>
<button>submit</button>
</form>
);
}
Explanation:
-
addfunction:- Receives
previousStateandformDataas arguments. - Processes the form data and returns a new state.
- Resets the form fields by calling
formData.reset(), which is a built-in method to reset form values.
- Receives
-
formaction handler:- Calls
formActionto handle the form submission. - Resets the local state for the
inputandselectelements to their initial values (""and"2"respectively).
- Calls
By incorporating these changes, the form submission will reset both the input and select elements properly.
I tried with above example and getting this error
I think it happed due to mismatching of args for useActionState
const [state, formAction] = useActionState(fn, initialState, permalink?);
src: https://react.dev/reference/react/useActionState
https://codesandbox.io/p/sandbox/react-dev-forked-qvpx9c?file=%2Fsrc%2FPage.js
@ujshaikh The expected behaviour is that the select component should behave the same way as the input component, when I click on the submit button, the action executes and then my 2 components should keep their state values, currently the input component is as expected and the select is reset to its default value instead of the one I chose before I submitted.
I've uploaded a gif of the demo.~
@Deba0099 I tried to setState the local state after the action is submitted, but the select component is still reset to the default value.
This is indeed a bug, thank you for reporting. Only uncontrolled components should be automatically reset when the action prop is used. The <select> here is controlled though and shouldn't change its value after the form is submitted.
https://github.com/user-attachments/assets/70a754d7-cb42-4b7f-bf54-d834db6765b8
-- https://codesandbox.io/p/sandbox/stupefied-cohen-n578l6
@cjg1122 Tried to reproduce and applied reset https://codesandbox.io/p/sandbox/react-dev-forked-qvpx9c
@cjg1122 fix: reset both input and select fields to default state after form submission
- Added useEffect to reset the 'name' input field and 'type' select field to their default states after form action is triggered.
useEffect(() => {
setName("");
setType("2");
}, [state]);
"use client";
import { useActionState, useState, useEffect } from "react";
function add() {
return Date.now();
}
export default function Page() {
const [state, formAction] = useActionState(add, 0);
const [name, setName] = useState("");
const [type, setType] = useState("2");
// Reset input and select states when action state changes
useEffect(() => {
setName(""); // Reset name input to empty
setType("2"); // Reset select to default value 2
}, [state]);
return (
<form action={formAction}>
<p>{state}</p>
<p>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</p>
<p>
<select
name="gender"
value={type}
onChange={(e) => setType(e.target.value)}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</p>
<button>submit</button>
</form>
);
}
- fix 👍 : reset both fields to default state after form submission using useRef hook
import { StrictMode, useState, useRef, version } from "react";
import { createRoot } from "react-dom/client";
function App() {
const [selectValue, setSelectValue] = useState("One");
const [textValue, setTextValue] = useState("Alpha");
const formRef = useRef(null);
return (
<form
ref={formRef}
action={async (formData) => {
console.log(Object.fromEntries(formData.entries()));
formRef.current.reset();
setSelectValue("One");
setTextValue("Alpha");
}}
>
<label>
select:
<select
name="selectValue"
onChange={(event) => {
setSelectValue(event.currentTarget.value);
}}
value={selectValue}
>
<option>One</option>
<option>Two</option>
</select>
controlled: {selectValue}
</label>
<br />
<label>
text:
<input
name="textValue"
onChange={(event) => {
setTextValue(event.currentTarget.value);
}}
value={textValue}
/>
controlled: {textValue}
</label>
<br />
<input type="submit" />
</form>
);
}
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<>
<p>React: {version}</p>
<App />
</>
);
check out code 1 here :https://codesandbox.io/p/sandbox/react-dev-forked-vfpgnn?file=%2Fsrc%2FPage.js%3A20%2C31
check out code 2 here : https://codesandbox.io/p/sandbox/react-controlled-select-subject-for-form-reset-forked-ng44jt
Is this bug still present? I could work on it in this case
No, it's not.
On Sun, 13 Oct 2024 at 2:09 PM, Daniele Berardi @.***> wrote:
Is this bug still present? I could work on it in this case
— Reply to this email directly, view it on GitHub https://github.com/facebook/react/issues/30580#issuecomment-2408885399, or unsubscribe https://github.com/notifications/unsubscribe-auth/A5IUT46JUCTTIEXSKUHYSETZ3IWTZAVCNFSM6AAAAABL4HOOQSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMBYHA4DKMZZHE . You are receiving this because you are subscribed to this thread.Message ID: @.***>
This issue is in fact still present. Including 19.0.0-rc.1, 19.0.0-beta-26f2496093-20240514 and 19.0.0-rc-7670501b-20241124
Now that React 19 is officially out, I can confirm this bug still persists.
yea same here
Setting defaultValue and key prevents the form from resetting upon submission.
const [data, submitAction, isPending] = useActionState(〇〇, initialState)
<select
key={data.fieldData.selectedOption}
id="select"
name="select"
defaultValue={data.fieldData.selectedOption}
>
<option value="">Please select</option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
Same problem for checkboxes that are controlled via useState. https://codesandbox.io/p/sandbox/react-controlled-checkbox-subject-for-form-reset-pvwgdm
Clicking the checkbox sets the checked state to true. After submitting the form, the checked state is still true (as reflected in the "checked is ...") part but the checkbox is not visibly selected. When rerender is triggered the checkbox is visibly selected again without changing the value of checked.
Edit: Only checkboxes inside the form seem to be subject to this bug.
Setting defaultValue and key prevents the form from resetting upon submission.
const [data, submitAction, isPending] = useActionState(〇〇, initialState)<select key={data.fieldData.selectedOption} id="select" name="select" defaultValue={data.fieldData.selectedOption} > <option value="">Please select</option> <option value="option1">Option 1</option> <option value="option2">Option 2</option> <option value="option3">Option 3</option> </select>
I have no clue why key props should be added there in order to prevent resetting upon submission.
But that key props really works to prevent reset
Any explanation?
Setting the key prop works for me too.
@flyingduck92 it really looks like an odd glitch/bug in the internal workings of React (my guess). There's no mention of key in the docs: https://react.dev/reference/react-dom/components/select#select
The controlled component also resets the "select" after the action is triggered. but the "input" component does not.
"use client"; import { useActionState, useState } from "react"; function add() { return Date.now(); } export default function Page() { const [state, formAction] = useActionState(add, 0); const [name, setName] = useState(""); const [type, setType] = useState("2"); return ( <form action={formAction}> <p>{state}</p> <p> <input type="text" value={name} onChange={(e) => setName(e.target.value)} /> </p> <p> <select name="gender" value={type} onChange={(e) => setType(e.target.value)} > <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> </p> <button>submit</button> </form> ); }Repro: https://codesandbox.io/p/sandbox/stupefied-cohen-n578l6 export default function Page() { const [state, formAction] = useActionState(add, 0); const [name, setName] = useState(""); const [type, setType] = useState("2"); return (
); }
Setting defaultValue and key prevents the form from resetting upon submission.
const [data, submitAction, isPending] = useActionState(〇〇, initialState)<select key={data.fieldData.selectedOption} id="select" name="select" defaultValue={data.fieldData.selectedOption} > <option value="">Please select</option> <option value="option1">Option 1</option> <option value="option2">Option 2</option> <option value="option3">Option 3</option> </select>
I just ran into the exact same issue but with uncontrolled selects. Using an uncontrolled select which receives it's defaultValue from the useActionState is not reflecting the updated value after submitting. When I add the key prop it's working as expected. This is not an issue with the html input element. I suppose this is a bug in react 19?
I suffered for hours on this on the exact issue. Setting the key worked for me.
What are we supposed to do if the input is controlled? we dont use defaultValue? even if I set value it still resets
setting key as the value doesnt work
can't use value and default value?
what the hell are we went to do lol, all my selects in the form just reset even if I havent changed the values
-
[ ]
y
minimal reproduction case on a nextjs15 sample project:
https://github.com/user-attachments/assets/6634eb7b-1f8c-4b1a-9d31-f64d552c2706
(select revert to defaultValue)
// app/pages.tsx
"use server"
import ClientForm from "./clientform";
import type {myType} from "./serverAction";
export default async function Home() {
const initialState: myType = {
select: 'A',
text: 'hello world',
};
return (
<main>
<ClientForm {...initialState} />
</main>
);
}
// app/serverAction.ts
"use server"
export type myType = {
select: string,
text: string,
}
export async function myServerAction(previousState: myType, postData: FormData) {
//previousState.select = 'C';
//return previousState;
const newState: myType = {
select: 'C',
text: 'goodbye',
};
return newState;
}
// app/clientform.tsx
"use client"
import { useActionState } from 'react';
import {myServerAction} from './serverAction';
import type {myType} from './serverAction';
export default function ClientForm(initialState: myType ) {
//const [stateAction, submitAction, isPending] = useActionState<myType>(myServerAction, initialState);
const [stateAction, submitAction, isPending] = useActionState(myServerAction, initialState);
console.log(stateAction);
return (
<form action={submitAction}>
<p>stateAction.select = {stateAction.select}</p>
<select defaultValue={stateAction.select} disabled={isPending}
>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select><br />
<input defaultValue={stateAction.text} /><br />
<button type="submit" disabled={isPending}>Send it</button>
</form>
);
}
// package.json
{
"name": "select",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "15.3.1"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.3.1",
"@eslint/eslintrc": "^3"
}
}
work around (fix) using old state components:
https://github.com/user-attachments/assets/2dbf9151-caac-4d40-a850-d4133c693220
(select remains on the selected option as i'm not using value/onChange combination, just defaultValue)
// app/clientform.tsx (without useActionState)
"use client"
import { useState } from 'react';
import { myServerAction } from './serverAction';
import type { myType } from './serverAction';
export default function ClientForm(initialState: myType) {
const [state, setState] = useState<myType>(initialState);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const newState = await myServerAction(state, formData);
setState(newState);
};
return (
<form onSubmit={handleSubmit}>
<p>state.select = {state.select}</p>
<select defaultValue={state.select} disabled={false}>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<br />
<input defaultValue={state.text} />
<br />
<button type="submit">Send it</button>
</form>
);
}
edit: 1. the comment out code are cases also tested that do not change anything. And 2. adding the old State management apis on top of useActionState also triggers the bug. It seems useActionState overrides everything with this broken behaviour, as mentioned on https://github.com/facebook/react/issues/30580#issuecomment-2265157518
ping @acdlite @eps1lon @rickhanlonii (sorry to bother, but it's been 9mo)
i tried to follow the changes that added useActionState and async Actions, but there are too many loose commits. I think one of you would tackle this better.
Literally one of the most annoying things im having to deal with in my current work ish aving to deal with this select reset stuff - have to add default values but then it doesn't work if you need state, add a key but that doesn't work for other reasons - unbelievably maddening
+1
Well, that's 2 days I won't get back... But the key prop does work 🎉
Well, that's 2 days I won't get back... But the key prop does work 🎉
the key prop might impact how things are rendered. you are faking that one element, that is the same between renders, is not the same anymore.
(wonder how many days this wasted of peoples lives in total. Might be a higher a denial of service attack on human intellect than sudoku)
I love and I hate how I predicted exactly that this would happen: https://github.com/facebook/react/issues/29034#issuecomment-2389645200.
Especially now that I hear that React is wilfully calling reset() on the <form> element: https://github.com/facebook/react/issues/29034#issuecomment-2843233452.
Given the time it takes for this issue to still not be solved, I truly believe it would have been – and would be – so much easier to simply offer a boolean option that defaults to true for resetting and doesn't reset when set to false. That would be non-breaking and could easily ship as a minor release.
Using onSubmit instead of the action attribute avoids this issue. The <select> value is preserved when using onSubmit.