Examining a bug(?) involving populating React state from URL query params
next dev
index.js
just parses the URL query string for a "v" param, which is used to initialize one of 5 buttons ("C" by default):
import {useRouter} from "next/router"
import {useState} from "react";
export const pathnameRegex = /[^?#]+/u
export default function Home() {
const searchStr = useRouter().asPath.replace(pathnameRegex, '')
const searchParams = new URLSearchParams(searchStr)
const initialValue = searchParams.get('v') || "C"
const [ value, setValue ] = useState(initialValue)
return (
<div>{
["A", "B", "C", "D", "E"].map(v => {
const disabled = v === value
console.log(`v: ${v}, value: ${value}, disabled: ${disabled}`)
return <input key={v} type={"button"} value={v} disabled={disabled} onClick={() => setValue(v)} />
})
}</div>
)
}
View 127.0.0.1:3000 ✅
open http://127.0.0.1:3000
Page renders without error, "C" is "active" (disabled) by default:
So far, so good.
Now try 127.0.0.1:3000?v=D ❌
Several problems are visible:
- "C" is disabled (instead of "D")
console.log
s imply that "D" should be disabled, and "C" should not be- Clicking "D" has no effect (something thinks it's already disabled)
- There's a console error about client and server "disabled" attributes not agreeing
- "A" and "C" are both disabled
- There's no way to un-disable "C"; it is stuck due to having been default during server render
console.log
s imply only "A" should be disabled
At first I thought this might have to do with the disabled
attribute being a boolean whose value is inferred from the presence or absence of the attribute (i.e. <input type="button" [disabled] />
as opposed to <input type="button" disabled="[yes|no]" />
).
However, the issue occurs if the "active" button is styled with e.g. font-weight: [bold|normal]
instead of toggling disabled
(see bold
branch):
- const disabled = v === value
- console.log(`v: ${v}, value: ${value}, disabled: ${disabled}`)
- return <input key={v} type={"button"} value={v} disabled={disabled} onClick={() => setVal>
+ const active = v === value
+ console.log(`v: ${v}, value: ${value}, active: ${active}`)
+ return <input key={v} type={"button"} value={v} style={{ fontWeight: active ? "bold" : "n>
- Page loads with "C" bolded, though "D" is supposed to be
- Clicking "A" results in "A" and "C" both bolded, though only "A" is supposed to be
- One difference from the
disabled
example is that you can click "C" here, and it resets the page to a consistent state:- "C" is understood to be the only "active" button (in the UI as well as
console.log
s) - Subsequent button clicks work as expected
- "C" is understood to be the only "active" button (in the UI as well as