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

  1. Add unique stable `key` properties to all list children: h('li', { key: item.id }, ...).
  2. Keep keys consistent between renders so maquette can match removed children.
  3. 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

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


AI-assisted analysis of HelloZeroNet/ZeroNet@454c0b2e7e (2026-09-02). Data as JSON: /api/errors/4a466abcaf0e2dc4. Report an issue: GitHub.