{"record":{"id":"52d1463da9eb5855","repo":"yewstack/yew","slug":"duplicate-key-for-a-node-in-a-loop-kind-loop-t","errorCode":null,"errorMessage":"duplicate key for a node in a `{loop_kind}`-loop\nthis will create elements with duplicate keys if the loop iterates more than once","messagePattern":"duplicate key for a node in a `(.+?)`-loop\nthis will create elements with duplicate keys if the loop iterates more than once","errorType":"validation","errorClass":"syn::Error","httpStatus":null,"severity":"error","filePath":"packages/yew-macro/src/html_tree/html_loop.rs","lineNumber":41,"sourceCode":"    body_stream: ParseStream,\n    loop_kind: &str,\n) -> syn::Result<(Vec<Stmt>, HtmlChildrenTree, TokenStream)> {\n    let stmts = parse_preamble_stmts(body_stream)?;\n\n    let body = HtmlChildrenTree::parse_delimited_with_nodes(body_stream)?;\n    let deprecations = super::check_unnecessary_fragment(&body);\n    // TODO: more concise code by using if-let guards (MSRV 1.95)\n    for child in body.0.iter() {\n        let HtmlTree::Element(element) = child else {\n            continue;\n        };\n\n        let Some(key) = &element.props.special.key else {\n            continue;\n        };\n\n        if is_contextless_pure(&key.value) {\n            return Err(syn::Error::new(\n                key.value.span(),\n                format!(\n                    \"duplicate key for a node in a `{loop_kind}`-loop\\nthis will create elements \\\n                     with duplicate keys if the loop iterates more than once\"\n                ),\n            ));\n        }\n    }\n\n    Ok((stmts, body, deprecations))\n}\n\n/// Emit a loop that accumulates its body children into a `VList`.\n///\n/// `loop_header` is the native Rust loop syntax without its body, e.g.\n/// `for #pat in #iter` or `while #cond`.\npub(super) fn emit_loop(\n    loop_header: TokenStream,","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/yewstack/yew/blob/0e4a05472fac4e5fce1befe60fa4a1e43a36b6a3/packages/yew-macro/src/html_tree/html_loop.rs#L23-L59","documentation":"`parse_loop_body` (shared by `for` and `while` in `html!`) scans each top-level element child that carries a `key` prop and rejects keys that are `is_contextless_pure` — string/int literals or multi-segment paths that cannot reference the loop variable (html_loop.rs:12-18, 40-48). Such a key evaluates identically on every iteration, which would create duplicate keys and break Yew's keyed `VList` diffing, so it is caught at compile time.","triggerScenarios":"`html! { for item in items { <li key=\"row\">{ item }</li> } }` (literal key) or `key={consts::ROW_KEY}` (multi-segment constant path). Keys referencing the loop binding, like `key={item.id}` or a single identifier, are not flagged.","commonSituations":"Prototyping with one item and a hardcoded key, copy-pasting a static key down a list of similar elements, or converting hand-written repeated markup into a loop without re-keying it.","solutions":["Derive the key from per-item unique data: `key={item.id}`","If items have no natural id, enumerate the iterator and key on the index: `for (i, item) in items.iter().enumerate() { … key={i} }`","Remove the key entirely so the list diffs by position — only safe for append-only, never-reordered lists"],"exampleFix":"// before\nhtml! { for item in items { <li key=\"row\">{ item.name }</li> } }\n\n// after\nhtml! { for (i, item) in items.iter().enumerate() { <li key={i}>{ item.name }</li> } }","handlingStrategy":"validation","validationCode":"// Convention check for review: a loop body's key expression should mention\n// the loop binding (item) or an enumerate index — never a bare literal:\n// for item in items { <li key={item.id}>…</li> }        // good\n// for (i, item) in items.iter().enumerate() { <li key={i}>…</li> } // good\n// for item in items { <li key=\"row\">…</li> }           // rejected by the macro","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make keyed lists derive keys from a stable per-item id (`key={item.id}`)","Default to `.enumerate()` and `key={i}` when items lack ids","Never paste a static key into markup that is about to become a loop — the macro will (correctly) reject it"],"tags":["rust","yew","html-macro","list-key","diffing","virtual-dom"],"backgroundTag":"duplicate-list-key","analyzedSha":"0e4a05472fac4e5fce1befe60fa4a1e43a36b6a3","analyzedAt":"2026-08-22T21:16:31.212Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}