{"record":{"id":"8f25ae50cc7f5ec2","repo":"yewstack/yew","slug":"unkeyed-child-in-fully-keyed-list","errorCode":null,"errorMessage":"unkeyed child in fully keyed list","messagePattern":"unkeyed child in fully keyed list","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/yew/src/dom_bundle/blist.rs","lineNumber":94,"sourceCode":"    fn patch(self, node: VNode, bundle: &mut BNode) -> Self {\n        test_log!(\"patching: {:?} -> {:?}\", bundle, node);\n        test_log!(\n            \"  parent={:?}, slot={:?}\",\n            self.parent.outer_html(),\n            self.slot\n        );\n        // Advance the next sibling reference (from right to left)\n        let next =\n            node.reconcile_node(self.root, self.parent_scope, self.parent, self.slot, bundle);\n        test_log!(\"  next_position: {:?}\", next);\n        Self { slot: next, ..self }\n    }\n}\n/// Helper struct implementing [Eq] and [Hash] by only looking at a node's key\nstruct KeyedEntry(usize, BNode);\nimpl Borrow<Key> for KeyedEntry {\n    fn borrow(&self) -> &Key {\n        self.1.key().expect(\"unkeyed child in fully keyed list\")\n    }\n}\nimpl Hash for KeyedEntry {\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        <Self as Borrow<Key>>::borrow(self).hash(state)\n    }\n}\nimpl PartialEq for KeyedEntry {\n    fn eq(&self, other: &Self) -> bool {\n        <Self as Borrow<Key>>::borrow(self) == <Self as Borrow<Key>>::borrow(other)\n    }\n}\nimpl Eq for KeyedEntry {}\n\nimpl BNode {\n    /// Assert that a bundle node is a list, or convert it to a list with a single child\n    fn make_list(&mut self) -> &mut BList {\n        match self {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/yewstack/yew/blob/0e4a05472fac4e5fce1befe60fa4a1e43a36b6a3/packages/yew/src/dom_bundle/blist.rs#L76-L112","documentation":"During keyed-list diffing, Yew wraps existing child bundles in KeyedEntry, whose Borrow<Key>/Hash/Eq impls (packages/yew/src/dom_bundle/blist.rs:94) unwrap each bundle's key. The keyed code path (apply_keyed, selected at blist.rs:504 only when both old and new lists are flagged fully keyed) assumes every child carries a key; if a child that reached a fully keyed list has none, .expect panics with 'unkeyed child in fully keyed list'. The same rule React has: in a keyed list every child must be keyed on every render.","triggerScenarios":"A list (fragment or iterator output) whose children are all keyed on one render, then a later render mixes in an unkeyed child (e.g. a static <li> appended next to keyed iterator items); conditionally dropping the key={...} prop on some children between renders; nested fragments where the outer list is treated as fully keyed but an inner node never got a key.","commonSituations":"Appending a footer/header element inside a keyed list fragment; refactoring a keyed iterator and leaving a placeholder element without a key; keys derived from Option (key={item.id} where id is sometimes None, producing an unkeyed child).","solutions":["Give every child of that list a stable key, including static siblings: <li key={\"footer\"}>{\"load more\"}</li>","If some children legitimately have no key, move them outside the keyed list into their own fragment or wrap them in a keyed element","Ensure key={...} is unconditional — never switch a child between keyed and unkeyed across renders"],"exampleFix":"// before\nhtml! {\n    <ul>\n        { for items.iter().map(|i| html! { <li key={i.id}>{ i.title.clone() }</li> }) }\n        <li>{ \"last updated just now\" }</li>\n    </ul>\n}\n\n// after\nhtml! {\n    <ul>\n        { for items.iter().map(|i| html! { <li key={i.id}>{ i.title.clone() }</li> }) }\n        <li key={\"footer\"}>{ \"last updated just now\" }</li>\n    </ul>\n}","handlingStrategy":"validation","validationCode":"// debug helper: assert every child of a list is keyed before returning html\n#[cfg(debug_assertions)]\nfn assert_fully_keyed(children: &[VNode]) {\n    for (i, c) in children.iter().enumerate() {\n        assert!(c.key().is_some(), \"child at index {i} is unkeyed inside a keyed list\");\n    }\n}","typeGuard":"// returns true when every node carries a key — use to choose keyed vs unkeyed rendering\nfn all_keyed(children: &[VNode]) -> bool { children.iter().all(|c| c.key().is_some()) }","tryCatchPattern":null,"preventionTips":["Key every child of a list including static siblings like footers or separators","Never make the key prop conditional on data — Option keys create unkeyed children","Keep keyed and unkeyed children in separate fragments/lists"],"tags":["rust","wasm","yew","virtual-dom","runtime-panic"],"backgroundTag":"missing-key-in-keyed-list","analyzedSha":"0e4a05472fac4e5fce1befe60fa4a1e43a36b6a3","analyzedAt":"2026-08-22T21:16:31.212Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}