next.js: [NEXT-1198] Next.js 13.0.1+ breaks radio buttons in both app directory and pages
Verify canary release
- I verified that the issue exists in the latest Next.js canary release
Provide environment information
Operating System:
Platform: linux
Arch: arm64
Version: #78-Ubuntu SMP Tue Apr 18 09:00:08 UTC 2023
Binaries:
Node: 19.8.1
npm: 9.5.1
Yarn: 1.22.19
pnpm: N/A
Relevant packages:
next: 13.3.1
eslint-config-next: N/A
react: 18.2.0
react-dom: 18.2.0
Which area(s) of Next.js are affected? (leave empty if unsure)
App directory (appDir: true)
Link to the code that reproduces this issue
https://github.com/christianjuth/next-pages-radio-bug
To Reproduce
Note: this bug appears in 13.0.1+ including 13.4.2-canary.3
This bug appears in both a pages/ and app/. It only appears in the pages/ route when app dir is enabled. In other words, enabling app dir seems to have an effect on the pages dir.
Recreate the bug with pages/
- Create a new Next.js app using
npx create-next-app@latest
(opt out of TS, eslint, and Tailwind for simplicity) - Make sure the app directory is enabled (next.config.js must use experimental.appDir = true prior to Next.js 13.4.0).
- Modify either pages/pages.jsx with the following:
import { useState } from 'react'; export default function Test() { const [selectedTopping, setSelectedTopping] = useState('Medium'); return ( <div className="flex flex-col items-center"> <input type="radio" name="topping" value="Regular" id="regular" checked={selectedTopping === 'Regular'} onChange={e => setSelectedTopping(e.target.value)} /> <label htmlFor="regular">Regular</label> <input type="radio" name="topping" value="Medium" id="medium" checked={selectedTopping === 'Medium'} onChange={e => setSelectedTopping(e.target.value)} /> <label htmlFor="medium">Medium</label> <input type="radio" name="topping" value="Large" id="large" checked={selectedTopping === 'Large'} onChange={e => setSelectedTopping(e.target.value)} /> <label htmlFor="large">Large</label> </div> ) }
- Run your app in dev mode using
yarn dev
. If you visit http://localhost:3000/pages you will notice the controlled radio button is initially selected but then flickers uncollected
Recreate the bug with app/
- Create a new Next.js app using
npx create-next-app@latest
(opt out of TS, eslint, and Tailwind for simplicity) - Make sure the app directory is enabled (next.config.js must use experimental.appDir = true prior to Next.js 13.4.0).
- Modify either app/page.js with the following:
'use client' import { useState } from 'react'; function Test() { const [selectedTopping, setSelectedTopping] = useState('Medium'); return ( <div className="flex flex-col items-center"> <input type="radio" name="topping" value="Regular" id="regular" checked={selectedTopping === 'Regular'} onChange={e => setSelectedTopping(e.target.value)} /> <label htmlFor="regular">Regular</label> <input type="radio" name="topping" value="Medium" id="medium" checked={selectedTopping === 'Medium'} onChange={e => setSelectedTopping(e.target.value)} /> <label htmlFor="medium">Medium</label> <input type="radio" name="topping" value="Large" id="large" checked={selectedTopping === 'Large'} onChange={e => setSelectedTopping(e.target.value)} /> <label htmlFor="large">Large</label> </div> ) } export default function Page() { return <Test />; }
- Run your app in dev mode using
yarn dev
. If you visit http://localhost:3000 you will notice the controlled radio button is initially selected but then flickers uncollected
Debugging further Inspecting the dom shows the following
<div class="flex flex-col items-center">
<input type="radio" name="topping" id="regular" value="Regular">
<label for="regular">Regular</label>
<input type="radio" name="topping" id="medium" value="Medium" checked="">
<label for="medium">Medium</label>
<input type="radio" name="topping" id="large" value="Large">
<label for="large">Large</label>
</div>
However, the input marked checked=""
is not checked and if we query the dom we can see document.getElementById("medium").checked = false
Describe the Bug
There seems to be an issue where React is initially rendering the radio input as selected and then it almost instantly looses it’s selected state. My guess is this has something to do with React’s strict mode. However, this issue doesn’t arise until I enable the appDir.
Expected Behavior
I would expect the controlled radio input selection to visually match the state in React.
Which browser are you using? (if relevant)
1.51.110 Chromium: 113.0.5672.77 (Official Build) (64-bit)
How are you deploying your application? (if relevant)
No response
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 15
- Comments: 24 (4 by maintainers)
Commits related to this issue
- Fix checkbox and radio hydration Fixes whatever part of https://github.com/facebook/react/issues/26876 and https://github.com/vercel/next.js/issues/49499 that https://github.com/facebook/react/pull/2... — committed to facebook/react by sophiebits 9 months ago
- Fix checkbox and radio hydration Fixes whatever part of https://github.com/facebook/react/issues/26876 and https://github.com/vercel/next.js/issues/49499 that https://github.com/facebook/react/pull/2... — committed to facebook/react by sophiebits 9 months ago
- Fix checkbox and radio hydration Fixes whatever part of https://github.com/facebook/react/issues/26876 and https://github.com/vercel/next.js/issues/49499 that https://github.com/facebook/react/pull/2... — committed to facebook/react by sophiebits 9 months ago
- Fix checkbox and radio hydration (#27401) Fixes whatever part of https://github.com/facebook/react/issues/26876 and https://github.com/vercel/next.js/issues/49499 that https://github.com/facebook/r... — committed to facebook/react by sophiebits 9 months ago
- Fix checkbox and radio hydration (#27401) Fixes whatever part of https://github.com/facebook/react/issues/26876 and https://github.com/vercel/next.js/issues/49499 that https://github.com/facebook/rea... — committed to facebook/react by sophiebits 9 months ago
- Fix checkbox and radio hydration (#27401) Fixes whatever part of https://github.com/facebook/react/issues/26876 and https://github.com/vercel/next.js/issues/49499 that https://github.com/facebook/r... — committed to alunyov/react by sophiebits 9 months ago
Fixed in v13.5.6-canary.1 via 0a80017d038e7de7ac16894d4ebc8d64cf7988eb
Setting manually the
checked
prop withuseEffect
can temporarily “resolves” the issue.e.g:
@apostolos is correct, after I checked the reproduction with that version, the bug is gone. Please upgrade!
so then it could be fixed smth like this:
@ozzyfromspace appreciate the help, but like the other hacks in this thread this doesn’t solve the underlying issue. I know there are multiple ways I can hack together a workaround, but that still doesn’t solve the underlying problem. I’ve also found when managing large scale projects it really easy to introduce lots of npm packages but difficult to remove them later when the project becomes bloated. I’m a fan of Radix, but I cant afford to introduce another UI library just to fix one bug.
To be honest, this isn’t a huge issue for me at the moment. I haven’t noticed it since opening up this thread. My main problem is I’ve come to trust the React team when they say something is stable. Maybe I’ve just gotten lucky, but I’ve never seen a bug like this in a production React build.
I’m realizing the Next.js team moves faster then the React time, and I think I’m going to need to be more careful when upgrading Next versions in the future. I don’t want to sound ungrateful, because it’s amazing what Next team has accomplished in a short time. I just can’t afford to have projects breaking like this when upgrading Next versions.
I’m sorry if I’m blowing the severity of this issue out of proportion. I just never though I’d have to worry about html primitives themselves breaking in a Next.js update.
defaultChecked
is also bugged. The radio button should be checked by default when usingdefaultChecked
, but when you reload the page, the radio button is briefly checked and then unckecked again. Here is an example: https://codesandbox.io/p/sandbox/inspiring-babycat-qz92hy@christianjuth tried with
next@latest
the bug still persist -> https://codesandbox.io/p/sandbox/smoosh-brook-z4s7ff With older versions (eg.next@13.3.0
) it works correctly -> https://codesandbox.io/p/sandbox/beautiful-http-wjnk57@sophiebits @timneutkens can you pls downgrade react-builtin
This is the incriminated commit that upgrades react to a version with broken controlled radio input https://github.com/vercel/next.js/commit/925bb3b02568ea19deee2baae074c800a834121c.
@christianjuth I’m facing the problem starting from
13.3.1
. Tried13.0.1
and13.0.2
as you indicated and works perfectly. Did you mean13.3.1+
? The bug is still present in the last13.4.5
Found that with
13.3.1-canary.16
works, with13.3.1-canary.17
don’t work.Same problem here with
next@13.3.5-canary.2
+node@18.16.0
+pnpm@7.32.2
. I just figured out that the reported bug is not happening when starting production server (pnpm build && pnpm start
).Instead I’m facing another (more cryptic) bug. Don’t know if it’s better to open a separate issue. Starting @christianjuth example on prod server all works great. If I change the
onChange
behaviour from sync to async this is what happens:pnpm build && pnpm start
Medium
radio checkedLarge
radio -> after 2 seconds theLarge
radio is checked + the console printsonChange
andonChange setTimeout
Medium
radio -> after 2 seconds theMedium
radio is checked + the console printsonChange
andonChange setTimeout
Large
radio -> the change happens instantly and no console logs are printed (it seems input are no more controlled by React)Regular
radio -> theMedium
radio will be checked and after 2 seconds theRegular
radio is checkedTo replicate I just changed few line of code in
app/page.tsx
:https://github.com/vercel/next.js/assets/7655943/edb79868-b08c-4ee7-a935-995a2933353b
@lsagetlethias thank you your fix worked for me. I was tearing my hair out last night trying to understand if it was me doing something wrong