Skip to content

[NEXT-1198] Next.js 13.0.1+ breaks radio buttons in both app directory and pages  #49499

Closed
@christianjuth

Description

@christianjuth

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/

  1. Create a new Next.js app using npx create-next-app@latest (opt out of TS, eslint, and Tailwind for simplicity)
  2. Make sure the app directory is enabled (next.config.js must use experimental.appDir = true prior to Next.js 13.4.0).
  3. 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>
      )
    }
  4. 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/

  1. Create a new Next.js app using npx create-next-app@latest (opt out of TS, eslint, and Tailwind for simplicity)
  2. Make sure the app directory is enabled (next.config.js must use experimental.appDir = true prior to Next.js 13.4.0).
  3. 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 />;
    }
  4. 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

NEXT-1198

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIssue was opened via the bug report template.linear: nextConfirmed issue that is tracked by the Next.js team.locked

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions