HelloZeroNet/ZeroNet · error · Error
${parentVNode.vnodeSelector} had a ${childNode.vnodeSelector
Error message
${parentVNode.vnodeSelector} had a ${childNode.vnodeSelector} child removed, but there were more than one. You must add unique key properties to make them distinguishable. What it means
The removal counterpart of the 'added' ambiguity: when a keyless child is removed, maquette searches remaining siblings for another child equal (same selector and key) to the removed one. If it finds more than one match it cannot tell which node was removed and throws rather than removing the wrong DOM node.
Source
Thrown at plugins/UiConfig/media/js/all.js:514
domNode.parentNode.removeChild(domNode);
}
};
var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {
var childNode = childNodes[indexToCheck];
if (childNode.vnodeSelector === '') {
return; // Text nodes need not be distinguishable
}
var properties = childNode.properties;
var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;
if (!key) {
for (var i = 0; i < childNodes.length; i++) {
if (i !== indexToCheck) {
var node = childNodes[i];
if (same(node, childNode)) {
if (operation === 'added') {
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.');
} else {
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.');
}
}
}
}
}
};
var createDom;
var updateDom;
var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {
if (oldChildren === newChildren) {
return false;
}
oldChildren = oldChildren || emptyArray;
newChildren = newChildren || emptyArray;
var oldChildrenLength = oldChildren.length;
var newChildrenLength = newChildren.length;
var transitions = projectionOptions.transitions;
var oldIndex = 0;View on GitHub (pinned to 454c0b2e7e)
Solutions
- Add unique stable `key` properties to all list children: h('li', { key: item.id }, ...).
- Keep keys consistent between renders so maquette can match removed children.
- Avoid rendering duplicates of identical keyless siblings; give each a distinguishing selector or key.
Example fix
// before
items.map(function (item) { return h('li', [item.text]); })
// after
items.map(function (item) { return h('li', { key: item.id }, [item.text]); }) Defensive patterns
Strategy: validation
Validate before calling
function assertKeyedBeforeRemoval(parentVNode) {
var counts = {};
(parentVNode.children || []).forEach(function (c) {
if (c && c.vnodeSelector) {
var k = (c.properties && c.properties.key) || c.vnodeSelector;
counts[k] = (counts[k] || 0) + 1;
}
});
Object.keys(counts).forEach(function (k) {
if (counts[k] > 1 && k.indexOf('.') === -1 && (parentVNode.children[0].properties && !parentVNode.children[0].properties.key)) {
console.warn('Ambiguous removable children for ' + k + '; add keys');
}
});
} Type guard
function childHasStableKey(child) {
return child && child.properties && child.properties.key != null;
} Try / catch
try {
projection.update(updatedVnode);
} catch (e) {
if (e.message.indexOf('child removed') !== -1 && e.message.indexOf('key properties') !== -1) {
console.error('Add keys so removals can be matched unambiguously');
}
throw e;
} Prevention
- Key all list children so removals are matched by key, not by structural equality.
- Avoid arrays of identical keyless siblings in any dynamic content.
- Test delete/filter flows, not just initial render.
- Keep keys stable across renders — never derive them from array position.
When it happens
Trigger: projection.update / projector render where a keyless child is dropped from a parent vnode that still contains two or more identical keyless children (same vnodeSelector), e.g. deleting one of three identical <li> items.
Common situations: Deleting items from unkeyed lists; filtering an array so one duplicate-looking item disappears; slices/splices on arrays of same-shaped vnodes.
Related errors
- ${parentVNode.vnodeSelector} had a ${childNode.vnodeSelector
- Provide a transitions object to the projectionOptions to do
- Style values must be strings
- Property "className" is not supported, use "class".
- "class" property may not be updated. Use the "classes" prope
AI-assisted analysis of HelloZeroNet/ZeroNet@454c0b2e7e (2026-09-02).
Data as JSON: /api/errors/4a466abcaf0e2dc4.
Report an issue: GitHub.