Same Key on Different-Type Siblings Duplicated a List
This article may contain affiliate links. Its content is not affected by advertising.
In short
Different-type siblings sharing a key inside the same parent is normally harmless, but arranged adjacently under conditional short-circuits, it duplicated a list on a date-change soft-nav rerender.
The short version
Different-type sibling elements sharing a key inside the same parent is normally a non-issue under React’s reconciliation. But when the elements are arranged under a combination of “same parent, adjacent, conditional short-circuit rendering,” a real production bug was observed: changing the target date via a ?date= soft nav duplicated the “visitor list” and pushed the “student callouts” list further down the page. “A shared key between siblings is undefined behavior” is a false generalization — the actual trigger conditions are much narrower.
Symptom
The editor screen holds the target date in ?date=, and a soft nav (a rerender within the same component tree, no page transition) switches dates. The visitor list and student callouts were rendered as adjacent siblings under the same parent, each gated by the conditional short-circuits showVisitors && ... / showCallouts && ....
// page.tsx:168-191 (before the fix, at 85d8a613)
{showVisitors || showCallouts ? (
<div className={boardLayout.grid} style={{ marginTop: "1rem" }}>
{showVisitors && board?.visitors ? (
<VisitorsEditor
key={date}
classId={classId}
date={date}
initialItems={board.visitors}
/>
) : null}
{showCallouts && board?.callouts ? (
<CalloutsEditor
key={date}
classId={classId}
date={date}
initialItems={board.callouts}
/>
) : null}
</div>
) : null}
VisitorsEditor and CalloutsEditor are different components (different types), but both carried the same string, key={date}, as their key. Switching the target date from 2026-06-21 to 2026-06-22 via soft nav caused the visitor list to duplicate into two copies, with the student-callouts list pushed down below them, in production.
Why
React’s diffing reconciles siblings by type and position. Since VisitorsEditor and CalloutsEditor are different types, sharing the same key normally shouldn’t cause them to be mistaken for each other. In other words, “different-type siblings sharing a key” alone can’t be pinned down as the direct cause of this bug.
The duplication actually happened when three conditions coincided:
- The two elements belong to the same parent
- Their presence toggles via a conditional short-circuit (
showVisitors && .../showCallouts && ...) - The two elements are rendered adjacent to each other
During the rerender triggered by the date change (soft nav), when siblings with an identical key (key={date}) conditionally enter and exit under this arrangement, React’s internal reconciliation of the child list produced unexpected behavior. The review deliberately avoided recording this as “a key collision between different-type siblings is generally undefined” — what was actually observed is narrower: “this specific arrangement, under this specific render path, duplicates.” A broader generalization was never substantiated.
Fixing it
The two sibling elements were merged into a single independent component, VisitorsCalloutsSection, each given a non-colliding, stable key.
// VisitorsCalloutsSection.tsx:1-69 (after the fix, at 7111c0286617)
"use client";
import type { ClassVisitor, StudentCallout } from "@/types/db";
import { CalloutsEditor } from "./CalloutsEditor";
import { VisitorsEditor } from "./VisitorsEditor";
import boardLayout from "./board-layout.module.css";
export function VisitorsCalloutsSection({
classId, date, showVisitors, showCallouts, visitors, callouts,
}: {
classId: string; date: string; showVisitors: boolean; showCallouts: boolean;
visitors: ClassVisitor[] | null; callouts: StudentCallout[] | null;
}) {
if (!(showVisitors || showCallouts)) { return null; }
return (
<div className={boardLayout.grid} style={{ marginTop: "1rem" }}>
{showVisitors && visitors ? (
<VisitorsEditor key={`visitors-${date}`} classId={classId} date={date} initialItems={visitors} />
) : null}
{showCallouts && callouts ? (
<CalloutsEditor key={`callouts-${date}`} classId={classId} date={date} initialItems={callouts} />
) : null}
</div>
);
}
The intent behind the original key={date} — reinitialize with fresh data when the target date changes — was correct and preserved, by still including date inside each element’s new, non-colliding key (visitors-${date} / callouts-${date}). The parent (page.tsx) now just calls this single component, eliminating the arrangement — same parent, adjacent, conditional short-circuit — that had made the bug possible in the first place.
Regression coverage comes from a test that switches dates back and forth and asserts the “visitor list” heading always appears exactly once.
// visitors-callouts-section.test.tsx (excerpt, intent of the relevant assertion)
// 2026-06-22: with data present → with a colliding key, this is where "visitor list" duplicated into two.
// Across render → rerender cycles switching the target date through
// 2026-06-21 → 06-22 → 06-23 (alternating with and without data),
// screen.getAllByRole("heading", { name: "visitor list", level: 2 }) always stays at length 1.
Why wasn’t this caught
During the investigation, the first explanation proposed was the broad generalization “different-type sibling elements sharing a key causes undefined behavior.” Review pushed back with a counterexample — “React reconciles by type and position, so as long as the types differ this is normally harmless” — and that generalization was rejected. Instead, only the arrangement that actually triggered it (same parent, adjacent, conditional short-circuit, identical key) was recorded as an observed fact, and the fix removed that arrangement from the code entirely via the component split. What remains is not the overly broad lesson “a shared key is dangerous,” but the narrow, reproduction-matched one: “don’t give the same key to siblings that conditionally enter and exit under the same parent.”
Frequently asked questions
Q1Does sharing a key between different-type sibling elements always cause duplication?
No. React reconciles siblings by type and position, so different-type elements sharing a key is normally harmless. Duplication here needed a specific combination — same parent, adjacent, conditional short-circuit rendering — so 'a shared key is undefined behavior' is a false generalization.
Q2What is a soft nav, and why does it matter here?
It's a rerender triggered by changing only a query parameter (?date=) in the same component tree, no page transition. Unlike a full navigation that mounts and unmounts, the existing component instance is reused as-is, so its key directly controls when internal state reinitializes.
Q3Was the fix just changing the key values?
Not only that — the two sibling elements were also restructured into a single independent component. The original key={date}'s intent (reinitialize with new data when the date changes) was preserved by still including date in the new, non-colliding keys (visitors-${date} / callouts-${date}).
Environment verified
- Next.js ^16.0.0 / React ^19.0.0
- Observed in production on 2026-06-21, fixed 2026-06-22 (PR #1123)
What this article is based on
- TypeScript file lines 168-191commit 85d8a61
- TypeScript file lines 1-69commit 7111c02
- TypeScript file lines 1-90commit 7111c02
Every claim in this article comes from the records above. The repositories we operate are private so we cannot link to them, but which file, which lines, and at which commit we read them is recorded for every article. Nothing here is written from guesswork.