Review
Hydration-safe ids on tasting-note fields
CellarLog's SSR tasting-note form was generating label/input ids with a module-level counter, so the server HTML ids often diverged from the client remount and threw hydration warnings. Switch field wiring to React.useId so htmlFor/id pairs stay stable across server and client renders.
ReactTier 2useIdhydrationa11yssr
Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.
src/cellar/TastingNoteFields.tsx+4-8
| 1 | - | let nextFieldId = 0; | |
| 2 | - | ||
| 3 | - | function fieldId(prefix: string) { | |
| 4 | - | nextFieldId += 1; | |
| 5 | - | return `${prefix}-${nextFieldId}`; | |
| 6 | - | } | |
| 1 | + | import { useId } from "react"; | |
| 7 | 2 | ||
| 8 | 3 | type Props = { | |
| 9 | 4 | vintage: string; | |
| 10 | 5 | aroma: string; | |
| 11 | 6 | onChange: (patch: Partial<{ vintage: string; aroma: string }>) => void; | |
| 12 | 7 | }; | |
| 13 | 8 | ||
| 14 | 9 | export function TastingNoteFields({ vintage, aroma, onChange }: Props) { | |
| 15 | - | const vintageId = fieldId("vintage"); | |
| 16 | - | const aromaId = fieldId("aroma"); | |
| 10 | + | const baseId = useId(); | |
| 11 | + | const vintageId = `${baseId}-vintage`; | |
| 12 | + | const aromaId = `${baseId}-aroma`; | |
| 17 | 13 | ||
| 18 | 14 | return ( | |
| 19 | 15 | <fieldset className="tasting-note-fields"> | |
| 20 | 16 | <legend>Bottle notes</legend> | |
| 21 | 17 | <label htmlFor={vintageId}>Vintage year</label> | |
| 22 | 18 | <input | |
| 23 | 19 | id={vintageId} | |
| 24 | 20 | value={vintage} | |
| 25 | 21 | onChange={(e) => onChange({ vintage: e.target.value })} | |
| 26 | 22 | /> | |
| 27 | 23 | <label htmlFor={aromaId}>Primary aroma</label> | |
| 28 | 24 | <input | |
| 29 | 25 | id={aromaId} | |
| 30 | 26 | value={aroma} | |
| 31 | 27 | onChange={(e) => onChange({ aroma: e.target.value })} | |
| 32 | 28 | /> | |
| 33 | 29 | </fieldset> | |
| 34 | 30 | ); | |
| 35 | 31 | } |