{"record":{"id":"a551a7f8ea8f843d","repo":"preactjs/preact","slug":"component-s-key-property-should-be-a-function","errorCode":null,"errorMessage":"Component's \"${key}\" property should be a function, but got [${typeof vnode.props[key]}] instead\n${serializeVNode(vnode)}\n\n${getOwnerStack(vnode)}","messagePattern":"Component's \"(.+?)\" property should be a function, but got \\[(.+?)\\] instead\n(.+?)\n\n(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"debug/src/debug.js","lineNumber":194,"sourceCode":"\t\t\t!('$$typeof' in vnode) // allow string refs when preact-compat is installed\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t`Component's \"ref\" property should be a function, or an object created ` +\n\t\t\t\t\t`by createRef(), but got [${typeof vnode.ref}] instead\\n` +\n\t\t\t\t\tserializeVNode(vnode) +\n\t\t\t\t\t`\\n\\n${getOwnerStack(vnode)}`\n\t\t\t);\n\t\t}\n\n\t\tif (typeof vnode.type == 'string') {\n\t\t\tfor (const key in vnode.props) {\n\t\t\t\tif (\n\t\t\t\t\tkey[0] === 'o' &&\n\t\t\t\t\tkey[1] === 'n' &&\n\t\t\t\t\ttypeof vnode.props[key] != 'function' &&\n\t\t\t\t\tvnode.props[key] != null\n\t\t\t\t) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Component's \"${key}\" property should be a function, ` +\n\t\t\t\t\t\t\t`but got [${typeof vnode.props[key]}] instead\\n` +\n\t\t\t\t\t\t\tserializeVNode(vnode) +\n\t\t\t\t\t\t\t`\\n\\n${getOwnerStack(vnode)}`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check prop-types if available\n\t\tif (typeof vnode.type == 'function' && vnode.type.propTypes) {\n\t\t\tif (\n\t\t\t\tvnode.type.displayName === 'Lazy' &&\n\t\t\t\twarnedComponents &&\n\t\t\t\t!warnedComponents.lazyPropTypes.has(vnode.type)\n\t\t\t) {\n\t\t\t\tconst m =\n\t\t\t\t\t'PropTypes are not supported on lazy(). Use propTypes on the wrapped component itself. ';","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/preactjs/preact/blob/e881e7e8386d7a430f87498a41c6a532992b4599/debug/src/debug.js#L176-L212","documentation":"Thrown from options._diff when the vnode type is a string (a host element like 'div') and a prop key starting with 'on' (onClick, onChange, etc.) is set to a non-null, non-function value. Preact treats on* props as event handlers; a non-function there would never fire and indicates a wiring bug. The message names the offending key and reports the actual typeof.","triggerScenarios":"Passing a string instead of a handler: `<button onClick='submit'>`; passing an object/number: `onChange={value}` where value is the state; destructuring `const { onClick } = props` then `<div onClick={onClick}/>` when onClick is undefined-but-not-null via a default; binding issues that yield a primitive; spread props where one on* key is a config value.","commonSituations":"Passing the value of an input into onChange instead of the handler: `onChange={text}` (state) vs `onChange={setText}`; mock or test prop objects where handlers are stubbed with strings; copy-paste of attribute name into handler slot; loose TS prop typing (`any`) hiding the mismatch until runtime.","solutions":["Replace the value with a function: `<button onClick={() => submit()}>` or `<button onClick={handleSubmit}>`.","If the handler is optional, pass a no-op default: `onClick={onClose ?? (() => {})}` rather than a primitive.","Check destructuring — ensure the variable bound to the on* prop is actually a function.","For spread props, validate the source object's on* keys are functions before spreading."],"exampleFix":"// before\n<input onChange={value} />\n\n// after\n<input value={value} onChange={setValue} />","handlingStrategy":"type-guard","validationCode":"function assertEventHandlers(props) {\n  for (const key in props) {\n    if (key.startsWith('on') && props[key] != null && typeof props[key] !== 'function') {\n      throw new Error(`Prop \"${key}\" must be a function, got ${typeof props[key]}`);\n    }\n  }\n  return props;\n}\n\n// before render\nconst safeProps = assertEventHandlers(inputProps);\nreturn <input {...safeProps} />;","typeGuard":"/** @param {string} key @param {unknown} v */\nfunction isValidEventHandler(key, v) {\n  if (!key.startsWith('on')) return true;\n  return v == null || typeof v === 'function';\n}","tryCatchPattern":null,"preventionTips":["Type on* props strictly: `onClick?: () => void` in TS prop interfaces.","When spreading props, validate the source object's on* keys are functions.","Never pass state values into on* slots — pass the setter or a handler."],"tags":["preact","event-handler","props","on-props","debug-build"],"backgroundTag":null,"analyzedSha":"e881e7e8386d7a430f87498a41c6a532992b4599","analyzedAt":"2026-08-13T04:27:04.532Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}