{"record":{"id":"469a7fba29bd6f4a","repo":"HelloZeroNet/ZeroNet","slug":"the-selector-for-the-root-vnode-may-not-be-changed","errorCode":null,"errorMessage":"The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)","messagePattern":"The selector for the root VNode may not be changed\\. \\(consider using dom\\.merge and add one extra level to the virtual DOM\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/UiConfig/media/js/all.js","lineNumber":673,"sourceCode":"                }\n            }\n            updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;\n            updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;\n            if (vnode.properties && vnode.properties.afterUpdate) {\n                vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n            }\n        }\n        if (updated && vnode.properties && vnode.properties.updateAnimation) {\n            vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);\n        }\n        vnode.domNode = previous.domNode;\n        return textUpdated;\n    };\n    var createProjection = function (vnode, projectionOptions) {\n        return {\n            update: function (updatedVnode) {\n                if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {\n                    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)');\n                }\n                updateDom(vnode, updatedVnode, projectionOptions);\n                vnode = updatedVnode;\n            },\n            domNode: vnode.domNode\n        };\n    };\n    ;\n    // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.\n    exports.h = function (selector) {\n        var properties = arguments[1];\n        if (typeof selector !== 'string') {\n            throw new Error();\n        }\n        var childIndex = 1;\n        if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {\n            childIndex = 2;\n        } else {","sourceCodeStart":655,"sourceCodeEnd":691,"githubUrl":"https://github.com/HelloZeroNet/ZeroNet/blob/454c0b2e7e000fda7000cba49027541fbf327b96/plugins/UiConfig/media/js/all.js#L655-L691","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nprojection.update(state.tab === 'a' ? h('div.panelA') : h('div.panelB'));\n// after\n// stable root, variable child\nprojection.update(h('div.panel', [state.tab === 'a' ? renderA() : renderB()]));","handlingStrategy":"validation","validationCode":"function assertSameRootSelector(prevVnode, nextVnode) {\n  if (prevVnode && nextVnode && prevVnode.vnodeSelector !== nextVnode.vnodeSelector) {\n    throw new Error('Root selector must stay constant: ' + prevVnode.vnodeSelector + ' -> ' + nextVnode.vnodeSelector);\n  }\n}\n// call before projection.update","typeGuard":"function sameRootSelector(prev, next) {\n  return prev == null || next == null || prev.vnodeSelector === next.vnodeSelector;\n}","tryCatchPattern":"try {\n  projection.update(next);\n} catch (e) {\n  if (e.message.indexOf('root VNode may not be changed') !== -1) {\n    console.error('Keep root selector constant; vary children instead');\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["maquette","projection","update","virtual-dom"],"backgroundTag":"root-selector-changed","analyzedSha":"454c0b2e7e000fda7000cba49027541fbf327b96","analyzedAt":"2026-09-02T19:46:57.278Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}