HelloZeroNet/ZeroNet · error · Error

The selector for the root VNode may not be changed. (conside

Error message

The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)

What it means

createProjection's update function throws when the updated root vnode has a different vnodeSelector than the one the projection was created with. maquette only diffs against the same node type at the root; changing the root selector requires a different DOM structure, so it suggests dom.merge with an extra wrapper level.

Source

Thrown at plugins/UiConfig/media/js/lib/maquette.js:484

                }
            }
            updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;
            updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;
            if (vnode.properties && vnode.properties.afterUpdate) {
                vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);
            }
        }
        if (updated && vnode.properties && vnode.properties.updateAnimation) {
            vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);
        }
        vnode.domNode = previous.domNode;
        return textUpdated;
    };
    var createProjection = function (vnode, projectionOptions) {
        return {
            update: function (updatedVnode) {
                if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {
                    throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');
                }
                updateDom(vnode, updatedVnode, projectionOptions);
                vnode = updatedVnode;
            },
            domNode: vnode.domNode
        };
    };
    ;
    // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.
    exports.h = function (selector) {
        var properties = arguments[1];
        if (typeof selector !== 'string') {
            throw new Error();
        }
        var childIndex = 1;
        if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {
            childIndex = 2;
        } else {

View on GitHub (pinned to 454c0b2e7e)

Solutions

  1. Keep the root vnode selector constant across updates.
  2. Wrap the variable content in a stable outer element, e.g. always return h('div', [conditionalContent]).
  3. Use dom.merge/dom.append with an extra wrapper level as the error message suggests.
  4. Tear down and re-create the projector/projection if the root must genuinely change.

Example fix

// before
projection.update(condition ? h('div', [x]) : h('span', [x]))
// after
projection.update(h('div', [condition ? x : y]))
Defensive patterns

Strategy: validation

Validate before calling

function assertSameRootSelector(prevVnode, nextVnode) {
  if (prevVnode.vnodeSelector !== nextVnode.vnodeSelector) {
    throw new Error('Root selector must stay constant: ' + prevVnode.vnodeSelector);
  }
}

Type guard

function sameRoot(prev, next) {
  return prev.vnodeSelector === next.vnodeSelector;
}

Try / catch

try {
  projection.update(updatedVnode);
} catch (e) {
  if (/selector for the root VNode may not be changed/.test(e.message)) {
    projection = projector.createDom(wrapInStableRoot(updatedVnode), extensions);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling projection.update(h('div', ...)) on a projection originally created from h('span', ...), or conditionally returning a different root element type from the top-level render function.

Common situations: Conditionally rendering different root tags (div vs span, or component vs null placeholder) from the root render function; refactoring a component's root element while a projector holds an old projection.

Related errors


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