Skip to content

[React 19] Controlled <select> component is subject to automatic form reset #30580

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
cjg1122 opened this issue Aug 2, 2024 · 23 comments
Open

Comments

@cjg1122
Copy link

cjg1122 commented Aug 2, 2024

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>
  );
}

20240802-193357

Repro: https://codesandbox.io/p/sandbox/stupefied-cohen-n578l6

@ujshaikh
Copy link

ujshaikh commented Aug 2, 2024

@cjg1122
Can you please add actual and expected behavior?

@Deba0099
Copy link

Deba0099 commented Aug 2, 2024

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:

  1. add function:

    • Receives previousState and formData as 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.
  2. form action handler:

    • Calls formAction to handle the form submission.
    • Resets the local state for the input and select elements to their initial values ("" and "2" respectively).

By incorporating these changes, the form submission will reset both the input and select elements properly.

@ujshaikh
Copy link

ujshaikh commented Aug 2, 2024

I tried with above example and getting this error
image

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

@cjg1122
Copy link
Author

cjg1122 commented Aug 2, 2024

@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.~

@cjg1122
Copy link
Author

cjg1122 commented Aug 2, 2024

@Deba0099 I tried to setState the local state after the action is submitted, but the select component is still reset to the default value.

@eps1lon eps1lon changed the title [React 19] The controlled component also resets the <select> after the action [React 19] Controlled <select> component is subject to automatic form reset Aug 2, 2024
@eps1lon
Copy link
Collaborator

eps1lon commented Aug 2, 2024

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.

Screen.Recording.2024-08-02.at.13.45.45.mov

-- https://codesandbox.io/p/sandbox/stupefied-cohen-n578l6

@ujshaikh
Copy link

ujshaikh commented Aug 2, 2024

@cjg1122
Tried to reproduce and applied reset
https://codesandbox.io/p/sandbox/react-dev-forked-qvpx9c

@AmanVerma2202
Copy link

AmanVerma2202 commented Aug 4, 2024

@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

@dberardi99
Copy link

Is this bug still present? I could work on it in this case

@AmanVerma2202
Copy link

AmanVerma2202 commented Oct 13, 2024 via email

@cima-alfa
Copy link

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

@cima-alfa
Copy link

Now that React 19 is officially out, I can confirm this bug still persists.

@aunruh
Copy link

aunruh commented Dec 10, 2024

yea same here

@watajam
Copy link

watajam commented Dec 12, 2024

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>

@goser
Copy link

goser commented Dec 13, 2024

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.

@flyingduck92
Copy link

flyingduck92 commented Dec 16, 2024

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?

@erfanimani
Copy link

erfanimani commented Jan 10, 2025

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

@Stormtrooper293
Copy link

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>
  );
}

20240802-193357 20240802-193357

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 (


{state}



<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>



<select
name="gender"
value={type}
onChange={(e) => setType(e.target.value)}
>
1
2
3


submit

);
}

@marcomuser
Copy link

marcomuser commented Jan 19, 2025

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?

@zeeshaanl
Copy link

I suffered for hours on this on the exact issue. Setting the key worked for me.

@JClackett
Copy link

JClackett commented Feb 19, 2025

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

@muhammedali2233445566778
  • [ ]

y

@gcb
Copy link

gcb commented Apr 22, 2025

minimal reproduction case on a nextjs15 sample project:

Screencast_20250422_220243.webm

(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:

Screencast_20250422_220354.webm

(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 #30580 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests