TypeScript

Fix 'props.handleSelect is not a function': wrong prop

Match the prop name on both sides of the component boundary to stop the crash, and see why optional chaining or a default value only hides it instead.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

props.handleSelect is undefined when this throws. Somewhere between the parent and the child, the callback was passed under one prop name and read under another — in the Stack Overflow question this fix is based on, onSelect is passed in and handleSelect is read out. Rename one side to match the other and the click works. For this cause, renaming is the only real fix: optional chaining and a default value both make the crash disappear without making the click do anything, which is worth knowing before you reach for either.

text
Uncaught TypeError: props.handleSelect is not a function

Why it happens#

MDN's entry for this TypeError describes the general mechanism plainly: "The JavaScript exception "is not a function" occurs when there was an attempt to call a value from a function, but the value is not actually a function," and that "Maybe there is a typo in the function name? Maybe the object you are calling the method on does not have this function?" None of that is React-specific — it is the same error calling any missing method on any plain object would throw.

What makes it React-specific is where the missing key comes from. React's docs on passing props are explicit that "React component functions accept a single argument, a props object" — there is no lookup magic; props is one plain object that JSX builds from the attributes written on the element. In the original question, Navbar writes <Menu2 ... onSelect={handleSelect}>, so the object Menu2 receives has a key named onSelect, not handleSelect. Reading props.handleSelect on that object is the same as reading any key nobody set: it returns undefined, and the next line calls it as if it were a function.

The mix-up is easy to make because both names look equally "correct" by React convention. React's docs on responding to events put it plainly: "By convention, event handler props should start with on, followed by a capital letter" — which is exactly why onSelect looks like the right prop name to write. handleSelect, meanwhile, is the equally conventional name for the function that implements a handler. The question's code gets both conventions right on their own, just on opposite sides of the component boundary: onSelect is a reasonable prop name, handleSelect is a reasonable function name, and Menu2 happens to have been written to read props.handleSelect, not props.onSelect.

Fix#

1. Confirm what the child actually receives#

The accepted answer's fastest check still applies: add console.log(props) inside Menu2 and read the key names in the logged object. If handleSelect is missing and something else is there instead (onSelect, onChange, onPick), this is the bug.

2. Rename one side to match the other#

Either change works, and the question's two answer threads each use one:

jsx
// Parent side: rename the prop so it matches what Menu2 reads
<Menu2
  placeholder="Navigate"
  options={["View Submissions", "Home"]}
  handleSelect={handleSelect}
/>
jsx
// Child side: leave the prop name alone, read the name it was actually given
function Menu2(props) {
  return props.options?.map((option) => (
    <div key={option} onClick={() => props.onSelect(option)}>
      {option}
    </div>
  ));
}

Picking one is a matter of which name fits the rest of your component, not a question of correctness — both remove the mismatch.

3. Prove the rename is what fixes it, not something else#

The accepted answer's final code changes two things at once: the prop name, and wrapping the callback in (option) => handleSelect(option). Isolating each change on a minimal version of the same pattern (no JSX, no hooks — this is JavaScript's own call semantics, not a Vite, Tailwind, or React-render detail) shows only the rename matters:

text
TypeError: props.handleSelect is not a function

is what you still get with the prop passed as onSelect, whether or not it is wrapped in an extra arrow function. Renaming only the passed key, from onSelect to handleSelect, replaces that crash with a normal call — reproduced on Node.js v26.7.0, 26 October 2026.

Verify the fix#

Click the option and check two things, not just that the error is gone: the console no longer throws, and the callback actually runs. Confirm whatever handleSelect is supposed to do (navigate, update state, log the choice) really happens. The next section is why that second check matters — a crash is easy to notice; a callback that silently never runs is not.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

Hiding the crash is not the same as fixing it#

Two common "fixes" remove the crash without touching the bug, and both are worth recognising because they make the problem quieter, not solved:

  • props.handleSelect?.(option) — optional chaining — returns undefined instead of throwing when the key is missing. Reproduced: the click completes with no error and no log at all, because handleSelect never runs.
  • A default value, function Menu2({ handleSelect = () => {} }), has the identical effect: no crash, no behaviour.

Both are legitimate defensive patterns for a prop that is genuinely optional. Neither is appropriate here, because handleSelect is not optional — the dropdown has no other way to report a selection — so silencing the crash just trades a loud bug for a silent one that is harder to file a bug report about.

Typed props remove the ambiguity earlier, at compile time rather than click time. Giving Menu2 a type that requires handleSelect: (option: string) => void, then writing the question's mismatch as JSX — <Menu2 onSelect={handleSelect} />, no cast — fails to compile on TypeScript 5.9.3:

text
repro-typescript-jsx.tsx(16,17): error TS2322: Type '{ onSelect: (option: string) => void; }' is not assignable to type 'IntrinsicAttributes & Menu2Props'.
  Property 'onSelect' does not exist on type 'IntrinsicAttributes & Menu2Props'.

That is the same broad category of mistake — a function passed as a prop going wrong — that Next.js flags for a related but different reason when a function crosses a Server/Client Component boundary. If the message you actually have is about event handlers or functions not being passable to a Client Component, that is a separate error with its own fix: see Fix Functions cannot be passed directly to Client Components. Which exact wording you get there depends on whether React treats your prop name as an event handler, per that page's own breakdown.

Not every props.X is not a function on this exact line comes from a renamed prop. Two other mistakes in the same few lines throw a closely related message:

  • Calling the function instead of passing it. Writing handleSelect: handleSelect('x') in the object passed to Menu2 (in JSX, handleSelect={handleSelect('x')}) calls handleSelect immediately with 'x' — which logs x right there — and stores its return value (undefined, since the function only logs) under the handleSelect key. The later click still throws the identical TypeError: props.handleSelect is not a function, for a different reason than a naming mismatch: reproduced with x printing before the crash, proving the function already ran once, with the wrong argument, before the real click ever happened.

  • Forgetting the destructuring braces. Writing the child as function Menu2(handleSelect) instead of function Menu2({ handleSelect }) binds the whole props object to the name handleSelect, not the function inside it. React's own docs recap the correct shape plainly: "To read props, use the function Avatar({ person, size }) destructuring syntax" — the braces are not optional punctuation. Calling the un-destructured object throws a related but distinguishable message, with no props. prefix:

    text
    TypeError: handleSelect is not a function

    reproduced with the prop name spelled correctly everywhere — only the function's parameter syntax was wrong.

If your own is not a function error is not on a prop at all, check what it is actually on before reaching for this fix. An imported library's default export failing the same way is a different bug with a different cause — see Fix Day.js: dayjs_1.default is not a function for a CommonJS/ESM interop mismatch instead of a naming one. A browser API behaving unexpectedly during server rendering is another cause behind a similar message: see Fix localStorage is not defined after 'use client' (Next.js), which reproduces most Node.js 25 releases exposing localStorage as an empty stand-in object during that render — so getItem is the piece missing, not the whole object — with one release behaving differently again.

This is a JavaScript and React fundamentals bug, not a Vite-, Tailwind-, or React-version-specific one: nothing above depended on a bundler, a CSS framework, or a particular React release, which is why every reproduction here runs in plain Node rather than a browser. The fix is always the same shape — log props, find the one key that does not match between where it is passed and where it is read, and rename one side of that single mismatch.

Further reading: Fix Vite React error #130: react-multi-carousel.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote