HelloZeroNet/ZeroNet · error · Error

${parentVNode.vnodeSelector} had a ${childNode.vnodeSelector

Error message

${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.

What it means

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.

Source

Thrown at plugins/UiConfig/media/js/all.js:512

        }
        if (domNode.parentNode) {
            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;

View on GitHub (pinned to 454c0b2e7e)

Solutions

  1. Add a unique `key` property to each child in the list: h('li', { key: item.id }, ...).
  2. Ensure keys are stable per item (database id), not array index, so additions/removals reconcile correctly.
  3. If items are genuinely indistinguishable, append new children at the end only, or restructure so each child has a unique selector.

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 assertKeyedChildren(vnode) {
  var children = (vnode.children || []).filter(function (c) { return c && c.vnodeSelector; });
  var selectors = {};
  children.forEach(function (c) {
    var key = c.properties && c.properties.key;
    if (!key) {
      selectors[c.vnodeSelector] = (selectors[c.vnodeSelector] || 0) + 1;
    }
  });
  Object.keys(selectors).forEach(function (s) {
    if (selectors[s] > 1) throw new Error('Duplicate keyless children with selector ' + s + '; add key properties');
  });
}

Type guard

function childrenAreKeyed(vnode) {
  return (vnode.children || []).every(function (c) {
    return !c || !c.vnodeSelector || (c.properties && c.properties.key != null);
  });
}

Try / catch

try {
  projection.update(updatedVnode);
} catch (e) {
  if (e.message.indexOf('unique key properties') !== -1) {
    console.error('Add stable key properties to list children: ' + e.message);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


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