react icon indicating copy to clipboard operation
react copied to clipboard

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

Open cjg1122 opened this issue 1 year ago • 36 comments

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

cjg1122 avatar Aug 02 '24 10:08 cjg1122

@cjg1122 Can you please add actual and expected behavior?

ujshaikh avatar Aug 02 '24 10:08 ujshaikh

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.

Deba0099 avatar Aug 02 '24 10:08 Deba0099

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

ujshaikh avatar Aug 02 '24 10:08 ujshaikh

@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 avatar Aug 02 '24 11:08 cjg1122

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

cjg1122 avatar Aug 02 '24 11:08 cjg1122

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

eps1lon avatar Aug 02 '24 11:08 eps1lon

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

ujshaikh avatar Aug 02 '24 11:08 ujshaikh

@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

AmanVerma2202 avatar Aug 04 '24 09:08 AmanVerma2202

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

dberardi99 avatar Oct 13 '24 08:10 dberardi99

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: @.***>

AmanVerma2202 avatar Oct 13 '24 08:10 AmanVerma2202

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

jeremy-step avatar Nov 28 '24 15:11 jeremy-step

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

jeremy-step avatar Dec 06 '24 14:12 jeremy-step

yea same here

aunruh avatar Dec 10 '24 19:12 aunruh

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>

watajam avatar Dec 12 '24 06:12 watajam

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.

goser avatar Dec 13 '24 09:12 goser

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?

flyingduck92 avatar Dec 16 '24 23:12 flyingduck92

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

erfanimani avatar Jan 10 '25 03:01 erfanimani

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

); }

Stormtrooper293 avatar Jan 10 '25 05:01 Stormtrooper293

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?

marcomuser avatar Jan 19 '25 08:01 marcomuser

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

zeeshaanl avatar Jan 23 '25 08:01 zeeshaanl

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

JClackett avatar Feb 19 '25 14:02 JClackett

  • [ ]

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

gcb avatar Apr 22 '25 22:04 gcb

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.

gcb avatar Apr 23 '25 17:04 gcb

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

JClackett avatar Apr 23 '25 17:04 JClackett

+1

Zammer8 avatar Apr 28 '25 17:04 Zammer8

Well, that's 2 days I won't get back... But the key prop does work 🎉

Hendo-getagent avatar Apr 29 '25 17:04 Hendo-getagent

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)

gcb avatar Apr 30 '25 14:04 gcb

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.

LutherTS avatar May 01 '25 09:05 LutherTS

Using onSubmit instead of the action attribute avoids this issue. The <select> value is preserved when using onSubmit.

issakujitsuk avatar Jul 23 '25 08:07 issakujitsuk