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
A projection returned by dom.append/merge/createDom caches its root vnode's selector; on update the new root vnode must keep the same vnodeSelector because the root DOM node itself cannot be replaced by the projector. Changing the root selector would require replacing the projection's root element, which maquette refuses.
Source
Thrown at plugins/UiConfig/media/js/all.js:673
}
}
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
- Keep the root vnodeSelector constant across updates; change only its properties and children.
- If the root must change, wrap it: project a stable outer node (e.g. h('div')) and swap the inner child each render.
- Use dom.merge and add one extra level to the virtual DOM, as the error message suggests.
- Create a new projection (dom.append again) when the root genuinely changes, replacing the old one.
Example fix
// before
projection.update(state.tab === 'a' ? h('div.panelA') : h('div.panelB'));
// after
// stable root, variable child
projection.update(h('div.panel', [state.tab === 'a' ? renderA() : renderB()])); Defensive patterns
Strategy: validation
Validate before calling
function assertSameRootSelector(prevVnode, nextVnode) {
if (prevVnode && nextVnode && prevVnode.vnodeSelector !== nextVnode.vnodeSelector) {
throw new Error('Root selector must stay constant: ' + prevVnode.vnodeSelector + ' -> ' + nextVnode.vnodeSelector);
}
}
// call before projection.update Type guard
function sameRootSelector(prev, next) {
return prev == null || next == null || prev.vnodeSelector === next.vnodeSelector;
} Try / catch
try {
projection.update(next);
} catch (e) {
if (e.message.indexOf('root VNode may not be changed') !== -1) {
console.error('Keep root selector constant; vary children instead');
}
throw e;
} Prevention
- Choose a stable root element (e.g. div.wrapper) for each projected component and never change it.
- Swap variable content as children of the stable root, not as the root itself.
- If the root element type must change, destroy and recreate the projection.
- Use dom.merge with an extra wrapper level when taking over existing DOM.
When it happens
Trigger: Calling projection.update(updatedVnode) where updatedVnode.vnodeSelector differs from the original root selector (e.g. root was h('div.app') and the update supplies h('section.app')); also when conditional rendering swaps the root element type.
Common situations: Toggling between two different root tags based on state; refactoring the top-level component's root element; misusing a projection whose natural lifetime is tied to one root node.
Related errors
- 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
- Functions may not be updated on subsequent renders (property
AI-assisted analysis of HelloZeroNet/ZeroNet@454c0b2e7e (2026-09-02).
Data as JSON: /api/errors/469a7fba29bd6f4a.
Report an issue: GitHub.