{"record":{"id":"1b43082ed79e35aa","repo":"HelloZeroNet/ZeroNet","slug":"parentvnode-vnodeselector-had-a-childnode-vno","errorCode":null,"errorMessage":"${parentVNode.vnodeSelector} had a ${childNode.vnodeSelector} child added, but there is now more than one. You must add unique key properties to make them distinguishable.","messagePattern":"(.+?) had a (.+?) child added, but there is now more than one\\. You must add unique key properties to make them distinguishable\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/UiConfig/media/js/all.js","lineNumber":512,"sourceCode":"        }\n        if (domNode.parentNode) {\n            domNode.parentNode.removeChild(domNode);\n        }\n    };\n    var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {\n        var childNode = childNodes[indexToCheck];\n        if (childNode.vnodeSelector === '') {\n            return;    // Text nodes need not be distinguishable\n        }\n        var properties = childNode.properties;\n        var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;\n        if (!key) {\n            for (var i = 0; i < childNodes.length; i++) {\n                if (i !== indexToCheck) {\n                    var node = childNodes[i];\n                    if (same(node, childNode)) {\n                        if (operation === 'added') {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');\n                        } else {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');\n                        }\n                    }\n                }\n            }\n        }\n    };\n    var createDom;\n    var updateDom;\n    var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {\n        if (oldChildren === newChildren) {\n            return false;\n        }\n        oldChildren = oldChildren || emptyArray;\n        newChildren = newChildren || emptyArray;\n        var oldChildrenLength = oldChildren.length;\n        var newChildrenLength = newChildren.length;","sourceCodeStart":494,"sourceCodeEnd":530,"githubUrl":"https://github.com/HelloZeroNet/ZeroNet/blob/454c0b2e7e000fda7000cba49027541fbf327b96/plugins/UiConfig/media/js/all.js#L494-L530","documentation":"When children without key properties are added, maquette tries to find the new child by comparing it against existing sibling vnodes. If more than one existing child matches (same vnodeSelector and same key — i.e. both keyless and identical selectors), maquette cannot determine which position the child belongs at and throws instead of guessing.","triggerScenarios":"Calling projection.update (or a projector render) where a new keyless child is inserted into a parent that already contains two or more other keyless children with the identical vnodeSelector (e.g. two h('li', ...) items, then a third is added).","commonSituations":"Rendering dynamic lists (todo items, table rows, menu entries) without key properties; lists where all items render the same tag/class; inserting items at the top/middle of a list rather than appending.","solutions":["Add a unique `key` property to each child in the list: h('li', { key: item.id }, ...).","Ensure keys are stable per item (database id), not array index, so additions/removals reconcile correctly.","If items are genuinely indistinguishable, append new children at the end only, or restructure so each child has a unique selector."],"exampleFix":"// before\nitems.map(function (item) { return h('li', [item.text]); })\n// after\nitems.map(function (item) { return h('li', { key: item.id }, [item.text]); })","handlingStrategy":"validation","validationCode":"function assertKeyedChildren(vnode) {\n  var children = (vnode.children || []).filter(function (c) { return c && c.vnodeSelector; });\n  var selectors = {};\n  children.forEach(function (c) {\n    var key = c.properties && c.properties.key;\n    if (!key) {\n      selectors[c.vnodeSelector] = (selectors[c.vnodeSelector] || 0) + 1;\n    }\n  });\n  Object.keys(selectors).forEach(function (s) {\n    if (selectors[s] > 1) throw new Error('Duplicate keyless children with selector ' + s + '; add key properties');\n  });\n}","typeGuard":"function childrenAreKeyed(vnode) {\n  return (vnode.children || []).every(function (c) {\n    return !c || !c.vnodeSelector || (c.properties && c.properties.key != null);\n  });\n}","tryCatchPattern":"try {\n  projection.update(updatedVnode);\n} catch (e) {\n  if (e.message.indexOf('unique key properties') !== -1) {\n    console.error('Add stable key properties to list children: ' + e.message);\n  }\n  throw e;\n}","preventionTips":["Give every child in a dynamic list a unique, stable key (item.id).","Never use array index as key for mutable/insertable lists.","Treat any repeated sibling vnodeSelector without keys as a bug.","Write render tests that add/remove list items to exercise reconciliation."],"tags":["maquette","keys","children","virtual-dom"],"backgroundTag":"missing-key-prop","analyzedSha":"454c0b2e7e000fda7000cba49027541fbf327b96","analyzedAt":"2026-09-02T19:46:57.278Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}