{"record":{"id":"94aea3648f99768f","repo":"usablica/intro.js","slug":"provided-callback-for-onafterchange-was-not-a-func","errorCode":null,"errorMessage":"Provided callback for onAfterChange was not a function","messagePattern":"Provided callback for onAfterChange was not a function","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/packages/tour/tour.ts","lineNumber":562,"sourceCode":"\n    this.callbacks.change = callback;\n    return this;\n  }\n\n  /**\n   * @deprecated onafterchange is deprecated, please use onAfterChange instead.\n   */\n  onafterchange(callback: introAfterChangeCallback) {\n    this.onAfterChange(callback);\n  }\n\n  /**\n   * Add a callback to be called after the tour changes steps\n   * @param {Function} callback callback function to be called\n   */\n  onAfterChange(callback: introAfterChangeCallback) {\n    if (!isFunction(callback)) {\n      throw new Error(\"Provided callback for onAfterChange was not a function\");\n    }\n\n    this.callbacks.afterChange = callback;\n    return this;\n  }\n\n  /**\n   * @deprecated oncomplete is deprecated, please use onComplete instead.\n   */\n  oncomplete(callback: introCompleteCallback) {\n    return this.onComplete(callback);\n  }\n\n  /**\n   * Add a callback to be called when the tour is completed\n   * @param {Function} callback callback function to be called\n   */\n  onComplete(callback: introCompleteCallback) {","sourceCodeStart":544,"sourceCodeEnd":580,"githubUrl":"https://github.com/usablica/intro.js/blob/e5517e6a24e0530f87885af9fb2a69bafd1d4a6b/src/packages/tour/tour.ts#L544-L580","documentation":"Tour class onAfterChange registers the callback run after each step change, stored in this.callbacks.afterChange. It throws this error when the argument is not a function, per the shared validation pattern across intro.js setter methods.","triggerScenarios":"introJs().onAfterChange() called with undefined/null/non-function — e.g., a typo'd config lookup or a callback name string instead of the function reference.","commonSituations":"Copy-pasting old intro.js v0.x snippet styles; bundler tree-shaking removing an exported handler referenced by string; optional callbacks in strict config pipelines arriving undefined.","solutions":["Pass a real function: introJs().onAfterChange(function(targetElement){ ... }).","Fix the undefined source: correct the config key, import, or variable name.","Coalesce with a default: introJs().onAfterChange(cfg.afterChange || (() => {})).","Move registration into initialization code that runs after handlers are defined.","Add a caller-side type guard before invoking the setter."],"exampleFix":"// before\nintroJs().onAfterChange(config.afterChange); // key is 'afterChangeCb'\n// after\nintroJs().onAfterChange(config.afterChangeCb);","handlingStrategy":"validation","validationCode":"const after = cfg.afterChangeCb;\nif (typeof after !== 'function') { throw new TypeError('afterChangeCb must be a function'); }\nintroJs().onAfterChange(after);","typeGuard":"const isCallable = (v: unknown): v is () => void => typeof v === 'function';","tryCatchPattern":"try {\n  intro().onAfterChange(cb);\n} catch (e) {\n  if (e.message.includes('onAfterChange was not a function')) {\n    console.error('onAfterChange callback invalid:', cb);\n    intro().onAfterChange(() => {});\n  } else { throw e; }\n}","preventionTips":["Verify config keys exist before destructuring into callbacks","Provide no-op defaults for optional lifecycle callbacks","Avoid passing minified/renamed exports indirectly","Call intro.js setters after handler definitions execute"],"tags":["callbacks","type-validation","api-misuse"],"backgroundTag":"callback-not-a-function","analyzedSha":"e5517e6a24e0530f87885af9fb2a69bafd1d4a6b","analyzedAt":"2026-08-31T22:12:26.007Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}