{"record":{"id":"03035eaf56e34be6","repo":"HelloZeroNet/ZeroNet","slug":"functions-may-not-be-updated-on-subsequent-renders","errorCode":null,"errorMessage":"Functions may not be updated on subsequent renders (property: ${propName}). Hint: declare event handler functions outside the render() function.","messagePattern":"Functions may not be updated on subsequent renders \\(property: (.+?)\\)\\. Hint: declare event handler functions outside the render\\(\\) function\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/UiConfig/media/js/all.js","lineNumber":433,"sourceCode":"                }\n            } else {\n                if (!propValue && typeof previousValue === 'string') {\n                    propValue = '';\n                }\n                if (propName === 'value') {\n                    if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {\n                        domNode[propName] = propValue;\n                        // Reset the value, even if the virtual DOM did not change\n                        domNode['oninput-value'] = undefined;\n                    }\n                    // else do not update the domNode, otherwise the cursor position would be changed\n                    if (propValue !== previousValue) {\n                        propertiesUpdated = true;\n                    }\n                } else if (propValue !== previousValue) {\n                    var type = typeof propValue;\n                    if (type === 'function') {\n                        throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');\n                    }\n                    if (type === 'string' && propName !== 'innerHTML') {\n                        if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                            domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                        } else {\n                            domNode.setAttribute(propName, propValue);\n                        }\n                    } else {\n                        if (domNode[propName] !== propValue) {\n                            domNode[propName] = propValue;\n                        }\n                    }\n                    propertiesUpdated = true;\n                }\n            }\n        }\n        return propertiesUpdated;\n    };","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/HelloZeroNet/ZeroNet/blob/454c0b2e7e000fda7000cba49027541fbf327b96/plugins/UiConfig/media/js/all.js#L415-L451","documentation":"Function-valued properties (typically event handlers) cannot change between renders; maquette treats functions as identity-immutable so it can skip deep-comparing them. This usually indicates a handler is recreated inside the render/renderMaquetteFunction on every render, which maquette considers a bug.","triggerScenarios":"During projection.update or a projector render pass, updateProperties encounters a function property (e.g. onclick) whose reference differs from the previous render — because the handler was defined inline in the render function.","commonSituations":"Inline arrow functions in JSX/render: h('button', { onclick: () => doThing(id) }); handlers built with .bind() inside render; moving from React (which accepts new handlers each render) to maquette.","solutions":["Declare the handler once outside the render function (module scope, constructor, or memoized) and reference it.","If the handler needs per-render data, capture state via closure over stable objects, or store mutable data on the domNode and read it in a stable handler.","Use maquette's projectors' eventHandlerInterceptor pattern or move dynamic data into vnode properties the handler can access (e.g. use data-* properties).","Wrap the changing function in a stable dispatcher created once that looks up the current behavior."],"exampleFix":"// before\nvar render = function () { return h('button', { onclick: function () { counter++; } }, ['inc']); };\n// after\nvar increment = function () { counter++; };\nvar render = function () { return h('button', { onclick: increment }, ['inc']); };","handlingStrategy":"type-guard","validationCode":"function assertFunctionsStable(prevVnode, nextVnode) {\n  // naive structural check across props\n  ['onclick','oninput','onchange','onkeydown'].forEach(function (ev) {\n    if (prevVnode && nextVnode && prevVnode.properties && nextVnode.properties &&\n        prevVnode.properties[ev] !== nextVnode.properties[ev]) {\n      console.warn('Handler ' + ev + ' changed identity between renders');\n    }\n  });\n}","typeGuard":"function isStableHandler(prev, next) {\n  return typeof prev !== 'function' || prev === next;\n}","tryCatchPattern":"try {\n  projection.update(vnode);\n} catch (e) {\n  if (e.message.indexOf('Functions may not be updated') !== -1) {\n    console.error('Move handler outside render(); property: ' + e.message);\n  }\n  throw e;\n}","preventionTips":["Define all event handlers once, outside the render function (module scope, class fields, closures created in constructors).","Never use inline arrow functions or .bind() inside render for handler properties.","For parameterized handlers, use a stable dispatcher that reads current state from a closure or the event target.","Enable the projector's eventHandlerInterceptor in development to catch handler churn early."],"tags":["maquette","event-handler","rerender","virtual-dom"],"backgroundTag":"function-property-updated","analyzedSha":"454c0b2e7e000fda7000cba49027541fbf327b96","analyzedAt":"2026-09-02T19:46:57.278Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}