Fix: TypeError: x is not a function
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix JavaScript TypeError is not a function caused by wrong variable types, missing imports, overwritten variables, incorrect method names, and callback issues.
What You Just Saw
I have seen this error on every JavaScript project I have ever worked on. It is the one that makes you question whether the function you called yesterday morning still exists this afternoon, and sometimes the answer is no, because a teammate renamed an export. You run JavaScript code and get:
TypeError: myFunction is not a functionOr variations:
TypeError: response.json is not a functionTypeError: arr.map is not a functionTypeError: document.getElementById(...).addEventListener is not a functionTypeError: (intermediate value)(...) is not a functionUncaught TypeError: this.setState is not a functionJavaScript tried to call something as a function, but the value is not a function. It could be undefined, null, a string, a number, an object, or any other non-function type.
Quick Reference Before You Dive In
If you arrived here from Google with a fresh stack trace, the five facts that resolve roughly 90 percent of cases:
- The variable named in the error is almost never undefined for the reason you think. A teammate renamed an export, the import path resolved to the wrong file, ESM/CJS interop wrapped the export in a namespace object, or a
letgot reassigned. Printtypeof yourVarandconsole.log(yourVar)at the call site first; the error rarely lies, but the assumed cause often does. The MDN TypeError: “x” is not a function reference is the canonical resource. response.json is not a functionmeans you called.json()on something that is not a fetch Response. Common causes: you already called.json()and got back parsed data, or you used axios (which returnsresponse.datainstead). The fix is one of those two; not retry, polyfill, or library upgrade.arr.map is not a functionmeansarris not actually an array. PrintArray.isArray(arr)to confirm. APIs that wrap data inside{ results: [...] }needdata.results.map(...), notdata.map(...).- Built-in method errors like
flat / at / replaceAll / toSortedare version errors. CallingArray.prototype.toSorted()on Node < 20 throws this error. Check your runtime version BEFORE writing a polyfill; the real fix is to upgrade Node or your build target. - In Safari, the error message names the property. Safari reports
TypeError: x.foo is not a function. (In 'x.foo()', 'x.foo' is undefined), which often tells you the whole fix. If you can reproduce there, do.
The rest of this article walks through each of those in detail, plus the failure modes most other guides skip.
How JavaScript Decides Something Is Not Callable
When you write something(), JavaScript expects something to be a function (or a callable object). If it is anything else (undefined, null, a number, a string, an object) the engine throws TypeError. The engine performs this check at the moment the call is made; it does not know in advance that the value is wrong, which is why the error appears at runtime even if your editor showed no warning.
The wording of the message varies by engine. V8 (Chrome, Node.js) says TypeError: x is not a function. SpiderMonkey (Firefox) says TypeError: x is not a function. JavaScriptCore (Safari) prefers TypeError: x.foo is not a function. (In 'x.foo()', 'x.foo' is undefined). The Safari wording is the most informative because it tells you which property was being called. If you can reproduce the error in Safari, the message often tells you the entire fix.
The error also surfaces when something is a function but is the wrong function. For example, new RegExp("foo") is a constructor call; if RegExp was shadowed by a local const RegExp = "literal";, the call fails with this exact message. The cause is rarely a typo in the call site itself; it is almost always a problem with the value being called or with the scope in which it was defined.
Common causes:
- Typo in the function name.
documnet.getElementByIdinstead ofdocument.getElementById. - Variable is not what you think. A variable was reassigned, overwritten, or shadowed by another declaration.
- Missing or wrong import. The module does not export the function you are trying to use.
- Calling a method on the wrong type.
"hello".map(): strings do not have amapmethod. - Calling parentheses on a non-function. Two expressions next to each other without a semicolon:
const a = {}(function() {})(). thiscontext lost. A method extracted from an object loses itsthisbinding.- Async/await mistake. Calling
.then()on a non-Promise, or forgetting toawaita function that returns an object.
Version History That Changes the Failure Mode
The JavaScript language and the Node.js runtime have added features over time that change which is not a function patterns can even occur. Knowing your engine version often eliminates the workaround you were about to apply.
- ES2015 (ES6, Jun 2015) introduced arrow functions,
classsyntax,const/let, default exports, andimport/export. Most of Fixes 1, 4, and 8 below only apply to ES2015+ code; pre-ES6 code usesvarand prototype-based “classes” that have their own pitfalls. - ES2017 / Node.js 7.6 (Feb 2017) stabilized
async/await. Before this, thePromisecallback chain in Fix 6 was the only option. After it,const data = await response.json();is the clean form and.json()chain bugs are rarer. - ES2019 added
Array.prototype.flatandflatMap. Calling.flat()on an old V8 (Node < 11) throwsflat is not a function. The fix is to upgrade Node, not to polyfill. - ES2020 (Node 14, Apr 2020 / Chrome 80, Feb 2020) introduced optional chaining (
?.) and nullish coalescing (??). Fix 9’sonSuccess?.(data)syntax only compiles in environments that support ES2020 or transpile to it. - ES2021 added
String.prototype.replaceAll. Calling"a".replaceAll(...)in older Safari (< 13.1) throwsreplaceAll is not a function. - ES2022 (Node 16+) stabilized top-level
await,Object.hasOwn,Error.cause,.at()on arrays and strings, and class fields. Top-levelawaitin ESM modules changes how module initialization can fail. A function imported from a module that throws during top-levelawaitbecomesundefinedand calling it raises this error. - Node 17 (Oct 2021) upgraded to OpenSSL 3, which broke many old crypto-related packages. Cryptic
is not a functionerrors from libraries pinned to OpenSSL 1.1.1 APIs traced back to this break. The workaround for legacy code is--openssl-legacy-provider; the real fix is to update the library. - ES2023 (Node 20, Apr 2023) added non-mutating array methods (
toSorted,toReversed,toSpliced,with). Callingarr.toSorted()on Node < 20 fails with this error. - ES2024 (Node 22, Apr 2024) finalized
Promise.withResolvers,Object.groupBy,Map.groupBy, andRegExpv-flag. Same pattern: calling these on older engines fails withis not a function. - Node 22 (Apr 2024) introduced experimental
require(esm)support, which finally lets CommonJS code synchronously require an ES module that has no top-levelawait. Earlier Node versions returned aPromise-wrapped namespace, sorequire("./esm-module.js").foo()failed withfoo is not a functionbecausefoowas on the resolved promise, not the require result. - Node 23 / Node 24 LTS roadmap (2024–2025) continues stabilizing ESM/CJS interop and the built-in test runner. Track release notes when an
is not a functionerror appears after a Node upgrade.
If your error is a built-in method like flat, at, replaceAll, toSorted, groupBy, or hasOwn, check your runtime version before writing a polyfill. The fix is almost always to upgrade.
When to Use Which Fix
The next nine sections cover the fixes in detail. The table below maps your situation to the recommended fix.
| Your situation | Recommended fix | Why |
|---|---|---|
| Not sure what the variable actually is | Fix 1: typeof and console.log at the call site | First step is always to inspect the value |
import { name } resolves to undefined | Fix 2: check default vs named export, inspect import * | Most common import bug |
arr.map / arr.filter is not a function | Fix 3: confirm Array.isArray(arr), access .results | API wraps array inside an object |
Class method called as callback loses this | Fix 4: bind or use arrow-function field | Methods lose this when extracted |
(intermediate value)(...) is not a function | Fix 5: missing semicolon (or prefix line with ;) | ASI does not insert before (, [ |
response.json is not a function | Fix 6: only call .json() on a Response, not on parsed data | One-shot consumption |
addEventListener("click", handleClick()) | Fix 7: pass handleClick, not handleClick() | Calling vs passing distinction |
User("Alice") instead of new User("Alice") | Fix 8: class constructors require new | Optional new is a strict error in ES6+ classes |
| Optional callback might not exist | Fix 9: onSuccess?.(data) | Optional chaining for safe call |
If multiple rows apply, pick the topmost one.
Fix 1: Check the Variable Type
Before calling something as a function, verify what it actually is:
console.log(typeof myFunction); // Should be "function"
console.log(myFunction); // See the actual value
console.log(typeof myFunction === "function"); // true or falseIf it prints undefined, the variable was never assigned a function. If it prints object, string, or number, it was assigned something else.
Common case: variable overwritten:
let filter = (arr) => arr.filter(x => x > 0);
// Later, accidentally overwritten:
const filter = document.getElementById("filter"); // Now it's an HTML element!
filter([1, -2, 3]); // TypeError: filter is not a functionRename one of the variables to avoid the conflict.
Personally I treat let as a code smell unless I can articulate why mutation is needed. With const, reassignment is a hard error at the point of the bug; with let, the function gets clobbered silently and the TypeError shows up four stack frames away. The few extra const keystrokes have paid for themselves more times than I can count.
Fix 2: Fix Missing or Wrong Imports
A common cause in module-based code. The function you imported might not exist in the module:
Broken: wrong import:
// The module exports { fetchUser } but you import fetchUsers (plural)
import { fetchUsers } from "./api.js";
fetchUsers(); // TypeError: fetchUsers is not a functionFixed:
import { fetchUser } from "./api.js";
fetchUser();Broken: default vs. named import confusion:
// Module exports: export default function myFunc() {}
import { myFunc } from "./module.js"; // Wrong — named import
myFunc(); // TypeError: myFunc is not a function
// Fixed:
import myFunc from "./module.js"; // Correct — default importCheck what the module exports:
import * as myModule from "./module.js";
console.log(myModule); // See all exportsFor module import resolution issues, see Fix: Module not found: Can’t resolve.
Fix 3: Fix .map() on Non-Arrays
arr.map is not a function usually means arr is not an array:
const data = fetchData(); // Returns an object, not an array
data.map(item => item.name); // TypeError: data.map is not a functionFix: Check if the value is an array:
console.log(Array.isArray(data)); // false
console.log(data); // { results: [...] }Fix: Access the actual array inside the response:
const data = fetchData();
data.results.map(item => item.name); // Access the array propertyFix: Convert to an array if needed:
// Object to array of entries
Object.entries(data).map(([key, value]) => `${key}: ${value}`);
// Object values to array
Object.values(data).map(item => item.name);
// String to array
const chars = Array.from("hello"); // ["h", "e", "l", "l", "o"]Fix: Handle the case where data might not be an array:
const items = Array.isArray(data) ? data : [];
items.map(item => item.name);If the data is coming from an API and might be null or undefined, see Fix: TypeError: Cannot read properties of undefined.
Fix 4: Fix this Context Issues
When you extract a method from an object, this is no longer bound to the object:
Broken:
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(this.tick, 1000); // `this` is lost inside setInterval
}
tick() {
this.seconds++; // TypeError: Cannot read properties of undefined
console.log(this.seconds);
}
}Fixed: bind the method:
start() {
setInterval(this.tick.bind(this), 1000);
}Fixed: use an arrow function:
start() {
setInterval(() => this.tick(), 1000);
}Fixed: define as an arrow function property:
class Timer {
seconds = 0;
tick = () => {
this.seconds++;
console.log(this.seconds);
};
start() {
setInterval(this.tick, 1000);
}
}Arrow functions capture this from the enclosing scope, so they always use the correct context.
React-specific: In React class components, event handlers lose this context:
class MyComponent extends React.Component {
handleClick() {
this.setState({ clicked: true }); // TypeError: this.setState is not a function
}
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}Fix by binding in the constructor or using an arrow function:
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}Or:
handleClick = () => {
this.setState({ clicked: true });
};Fix 5: Fix Missing Semicolons (IIFE Issues)
JavaScript’s automatic semicolon insertion (ASI) can cause unexpected is not a function errors:
Broken:
const a = { name: "Alice" }
(function() {
console.log("IIFE");
})()JavaScript interprets this as { name: "Alice" }(function() { ... })(), calling the object as a function.
Fixed: add a semicolon:
const a = { name: "Alice" };
(function() {
console.log("IIFE");
})();This happens whenever a line starts with (, [, or a template literal ` and the previous line does not end with a semicolon.
If I had to pick one JavaScript footgun that has bitten me most, it would be relying on automatic semicolon insertion without knowing its rules. ASI does NOT insert a semicolon before (, [, /, +, -, or `. When a no-semicolon codebase starts a line with one of those characters, the previous line “calls” the new line, and you get the cryptic (intermediate value)(...) is not a function. If your style omits semicolons, prefix those lines with a ;:
;(function() { ... })() ;[1, 2, 3].forEach(...)
Fix 6: Fix .json() and Fetch API Issues
response.json is not a function usually means you are calling .json() on something that is not a Response object:
Broken: already parsed:
fetch("/api/data")
.then(res => res.json())
.then(data => data.json()) // TypeError: data.json is not a functionAfter .json() is called once, the result is a plain object, not a Response. You cannot call .json() again.
Fixed:
fetch("/api/data")
.then(res => res.json())
.then(data => {
console.log(data); // Already parsed — use it directly
});Broken: response is not a fetch Response:
// axios returns parsed data directly, no .json() needed
const response = await axios.get("/api/data");
response.json(); // TypeError: response.json is not a function
// Fixed:
const data = response.data; // axios puts parsed data in .dataFix 7: Fix Callback and Higher-Order Function Issues
When passing functions as callbacks, make sure you pass the function, not the result of calling it:
Broken: calling the function instead of passing it:
button.addEventListener("click", handleClick());
// handleClick() is called immediately, and its return value
// (likely undefined) is passed as the listenerFixed: pass the function reference:
button.addEventListener("click", handleClick);Broken: wrong method name:
const arr = [1, 2, 3];
arr.forEach(item => {
console.log(item.toUpperCase()); // TypeError: item.toUpperCase is not a function
// item is a number, not a string
});Fixed:
const arr = [1, 2, 3];
arr.forEach(item => {
console.log(String(item)); // Convert to string first
});Fix 8: Fix Constructor and Class Issues
Broken: calling a class without new:
class User {
constructor(name) {
this.name = name;
}
}
const user = User("Alice"); // TypeError: Class constructor User cannot be invoked without 'new'Fixed:
const user = new User("Alice");Broken: importing a non-default export as a constructor:
// Module exports: export function createUser(name) { ... }
import createUser from "./user.js"; // Wrong — default import
const user = new createUser("Alice"); // TypeError: createUser is not a constructorFixed:
import { createUser } from "./user.js"; // Named import
const user = createUser("Alice"); // It's a factory function, not a classFix 9: Fix Optional Chaining for Safe Calls
If a function might not exist, use optional chaining:
// Might throw TypeError if onSuccess is undefined
onSuccess(data);
// Safe — only calls if onSuccess is a function
onSuccess?.(data);Check before calling:
if (typeof onSuccess === "function") {
onSuccess(data);
}Optional chaining (?.()) is cleaner for optional callbacks. Use the explicit check when you need to handle the missing function case differently.
When the Obvious Fixes Fail
If you have checked all the fixes above and still get this error, here are the less common causes I have personally chased down:
Check for circular dependencies. If module A imports from module B, and module B imports from module A, one of them might get undefined for the imported function. See Fix: cannot use import statement outside a module for related module issues.
Check for minification issues. Minifiers can rename or remove functions. If the error only appears in production (minified) code, check your build output and source maps.
Check for browser compatibility. Some functions are not available in older browsers. Array.at(), Object.hasOwn(), structuredClone() are relatively new. Check MDN for browser support tables.
Check for prototype pollution. If a library or your own code modifies Object.prototype or Array.prototype, built-in methods might be overwritten. Avoid modifying built-in prototypes.
Check for Web Workers or iframes. Arrays created in a different execution context (iframe, Web Worker) have a different Array constructor. instanceof Array returns false, and methods might not work as expected. Use Array.isArray() instead of instanceof Array.
Use the browser debugger. Set a breakpoint on the line that throws. Inspect the value being called to see exactly what it is and where it came from. The browser’s call stack shows how you got there.
Check ESM vs CommonJS interop. A CommonJS package that exports module.exports = function foo() {} imported with import foo from "pkg" gives you the function. Imported with import * as foo from "pkg" gives you a namespace whose .default is the function — and foo() throws foo is not a function. With Node’s --experimental-vm-modules and tooling like tsx, ts-node, vite-node, and jest --esm, the interop rules differ subtly. If you switched bundlers or runners recently, suspect this.
Check whether a TypeScript-only declaration is masking the truth. A .d.ts file may declare a value as () => Promise<T> while the actual JavaScript export is an object. The TypeScript compile passes, but the runtime call throws. Inspect the actual import at runtime with console.log(typeof imported).
Check tree-shaking and dead-code elimination. Aggressive bundlers (esbuild, Rollup, Turbopack) sometimes mis-classify a re-exported function as unused and drop it. The import resolves to undefined. Disable minification temporarily, repeat the build, and inspect the bundle.
Check whether a service worker is serving a stale chunk. In production SPAs, a service worker can serve an old chunk that no longer matches the current code. Functions that the new code expects do not exist on the old chunk. Hard-refresh, unregister the SW in DevTools (Application → Service Workers → Unregister), and try again.
What Other Tutorials Get Wrong About This Error
Most JavaScript tutorials list the same fixes but frame them in ways that produce subtle bugs.
They recommend polyfilling instead of upgrading. If your error is arr.toSorted is not a function, the cause is almost always an old Node or browser, not a missing polyfill. The polyfill adds bytes to every user, hides the version mismatch, and lets the bug recur in other ES2023+ methods. Check node --version and your build target first; polyfill only when you genuinely cannot upgrade.
They show instanceof Array instead of Array.isArray(). Arrays created in a different execution context (iframe, Web Worker, Node vm module) have a different Array constructor; instanceof Array returns false for them even though they ARE arrays. Array.isArray() is realm-agnostic and always correct. Tutorials written before iframes / workers were common still recommend the instanceof form.
They omit the ESM/CJS interop dimension. Tutorials that show import foo from "pkg" examples without mentioning that pkg’s module.exports = ... shape changes whether the default import works leave readers debugging “the import looks right but foo() throws.” Inspect with import * as foo from "pkg"; console.log(foo) to see the actual shape.
They confuse “not a function” with “not a constructor.” Calling a class without new produces “Class constructor X cannot be invoked without ‘new’” in strict mode, NOT “X is not a function.” Conversely, calling a regular function with new produces a different error. Tutorials that conflate the two miss the diagnostic value of the exact wording.
They miss the this-binding case in event handlers. button.addEventListener("click", obj.method) extracts the method and loses this. Tutorials that show event-listener wiring without addressing this send readers into a different TypeError two lines later (this.setState is not a function, this.fooHelper is not a function).
They skip the Safari diagnostic advantage. Safari’s TypeError: x.foo is not a function. (In 'x.foo()', 'x.foo' is undefined) message names the property, which often tells you the entire fix. Tutorials written from a Chrome-only perspective miss this and recommend more elaborate debugging than necessary.
Frequently Asked Questions
Why does the error say my function is undefined when I can see it imported at the top?
Three common causes. First, the export name does not match the import: the module exports fetchUser but you wrote import { fetchUsers }. Second, default vs named confusion: the module uses export default but you wrote import { name }. Third, ESM/CJS interop: a CommonJS package’s module.exports = function ... interacts differently depending on whether your runtime is Node CJS, Node ESM, Vite, esbuild, ts-node, or Jest. Inspect the actual import at runtime with import * as m from "pkg"; console.log(m).
Why does response.json is not a function happen?
You called .json() on something that is not a fetch Response object. Common causes: you already called .json() once (the result is parsed data, not a Response), you used axios (which auto-parses into response.data), or you used a library that returns an object with a different API surface than fetch. Check the type of the value with console.log(response.constructor.name).
Why does arr.map is not a function happen when I expect arr to be an array?
arr is not actually an array. Common shapes: an API wraps the array inside an object like { results: [...] }, the value is undefined because an upstream check failed, or it is a NodeList / HTMLCollection from document.querySelectorAll (which has forEach but not map). Confirm with Array.isArray(arr); if false, find the access pattern that gives you the actual array (arr.results, Array.from(arr), etc.).
What is the difference between “is not a function” and “Cannot read properties of undefined”?
“Is not a function” means the value exists but is not callable: it might be undefined, null, an object, or a string. “Cannot read properties of undefined” means you tried to access a property on undefined (which has no properties). The two errors often appear in adjacent code: data.user.name throws “Cannot read properties of undefined” if data.user is undefined; data.user.greet() throws “greet is not a function” if data.user.greet is undefined.
When should I use optional chaining for function calls?
Use fn?.(...) when the function is genuinely optional: an event callback the caller might omit, a config hook that may or may not be provided. Do not use it to silence “is not a function” errors on functions that should always exist; that hides the real bug. The pattern is “I know this might be missing and that is fine,” not “I do not know what is happening so let me swallow the error.”
Why does my React event handler throw “this.setState is not a function”?
The class method lost its this binding when extracted as a callback. onClick={this.handleClick} passes the method as a reference; inside it, this is undefined (or the DOM element in non-strict mode). Fix with arrow-function class field syntax (handleClick = () => { ... }) or bind in the constructor (this.handleClick = this.handleClick.bind(this)). Function components do not have this problem because they do not use this.
For TypeScript, the compiler catches most of these errors at compile time. If you are seeing this error in a TypeScript project, check for any types or incorrect type assertions that bypass type checking. See Fix: TypeScript property does not exist on type for related type issues.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Deno PermissionDenied — Missing --allow-read, --allow-net, and Other Flags
How to fix Deno PermissionDenied (NotCapable in Deno 2) errors — the right permission flags, path-scoped permissions, deno.json permission sets, and the Deno.permissions API.
Fix: Fastify Not Working — 404, Plugin Encapsulation, and Schema Validation Errors
How to fix Fastify issues — route 404 from plugin encapsulation, reply already sent, FST_ERR_VALIDATION, request.body undefined, @fastify/cors, hooks not running, and TypeScript type inference.
Fix: Vinxi Not Working — Dev Server Not Starting, Routes Not Matching, or Build Failing
How to fix Vinxi server framework issues — app configuration, routers, server functions, middleware, static assets, and deployment to different platforms.
Fix: Clack Not Working — Prompts Not Displaying, Spinners Stuck, or Cancel Not Handled
How to fix @clack/prompts issues — interactive CLI prompts, spinners, multi-select, confirm dialogs, grouped tasks, cancellation handling, and building CLI tools with beautiful output.