HelloZeroNet/ZeroNet · error · Error

Functions may not be updated on subsequent renders (property

Error message

Functions may not be updated on subsequent renders (property: ${propName}). Hint: declare event handler functions outside the render() function.

What it means

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.

Source

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

                }
            } else {
                if (!propValue && typeof previousValue === 'string') {
                    propValue = '';
                }
                if (propName === 'value') {
                    if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {
                        domNode[propName] = propValue;
                        // Reset the value, even if the virtual DOM did not change
                        domNode['oninput-value'] = undefined;
                    }
                    // else do not update the domNode, otherwise the cursor position would be changed
                    if (propValue !== previousValue) {
                        propertiesUpdated = true;
                    }
                } else if (propValue !== previousValue) {
                    var type = typeof propValue;
                    if (type === 'function') {
                        throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');
                    }
                    if (type === 'string' && propName !== 'innerHTML') {
                        if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
                            domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
                        } else {
                            domNode.setAttribute(propName, propValue);
                        }
                    } else {
                        if (domNode[propName] !== propValue) {
                            domNode[propName] = propValue;
                        }
                    }
                    propertiesUpdated = true;
                }
            }
        }
        return propertiesUpdated;
    };

View on GitHub (pinned to 454c0b2e7e)

Solutions

  1. Declare the handler once outside the render function (module scope, constructor, or memoized) and reference it.
  2. 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.
  3. Use maquette's projectors' eventHandlerInterceptor pattern or move dynamic data into vnode properties the handler can access (e.g. use data-* properties).
  4. Wrap the changing function in a stable dispatcher created once that looks up the current behavior.

Example fix

// before
var render = function () { return h('button', { onclick: function () { counter++; } }, ['inc']); };
// after
var increment = function () { counter++; };
var render = function () { return h('button', { onclick: increment }, ['inc']); };
Defensive patterns

Strategy: type-guard

Validate before calling

function assertFunctionsStable(prevVnode, nextVnode) {
  // naive structural check across props
  ['onclick','oninput','onchange','onkeydown'].forEach(function (ev) {
    if (prevVnode && nextVnode && prevVnode.properties && nextVnode.properties &&
        prevVnode.properties[ev] !== nextVnode.properties[ev]) {
      console.warn('Handler ' + ev + ' changed identity between renders');
    }
  });
}

Type guard

function isStableHandler(prev, next) {
  return typeof prev !== 'function' || prev === next;
}

Try / catch

try {
  projection.update(vnode);
} catch (e) {
  if (e.message.indexOf('Functions may not be updated') !== -1) {
    console.error('Move handler outside render(); property: ' + e.message);
  }
  throw e;
}

Prevention

When it happens

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

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

Related errors


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