Skip to main content
a78d43a0cd7059a7a66b
·4 min read

Why `onClick={() => ref.current?.submit()}` Works but `onClick={ref.current?.submit}` Doesn’t in React

react
function
typescript
4d5fd787ac74e0caa4f7

Sohan R. Emon

Developer, Learner, Tech Enthusiast

In React, the difference between these two lines might look small — but it hides a subtle and important detail about when and how your code runs.

tsx
// ✅ Works
onClick={() => formRef.current?.submit()}

// ❌ Doesn't work
onClick={formRef.current?.submit}

If you’ve ever hit this issue while using a ref to trigger a form submission, you’re not alone. Let’s unpack why it happens.


The Core Concept: Passing vs. Calling a Function

React’s event handlers expect a function reference, not a function call.

For example:

tsx
<button onClick={handleClick}>Click me</button>

Here, onClick receives the function reference handleClick. React will call it later when the button is clicked.

But if you write:

tsx
<button onClick={handleClick()}>Click me</button>

You’re calling handleClick() immediately during render. Whatever that function returns is what React tries to use as the click handler — usually undefined. That’s why this version doesn’t work.


How This Applies to formRef.current?.submit

Refs in React are populated after the component renders. During the first render:

tsx
formRef.current === null

So when you write:

tsx
onClick={formRef.current?.submit}

React evaluates this expression immediately while rendering. Since formRef.current is null at that time, formRef.current?.submit evaluates to undefined.

Result:

tsx
onClick={undefined}

React ends up attaching no click handler at all. Even after the ref is later populated, React won’t magically rebind it — it already set the handler to undefined on the first render.


Why the Arrow Function Version Works

Now compare it to:

tsx
onClick={() => formRef.current?.submit()}

This time, React receives a function reference (an arrow function). That arrow function isn’t executed during render — it’s only executed when the user clicks.

When the click actually happens, formRef.current already exists, so formRef.current?.submit() runs successfully.


Another Gotcha: Losing this Context

Even if formRef.current existed during render, onClick={formRef.current.submit} could still fail because you’re detaching the method from its original object.

In JavaScript, methods that depend on this lose their context when passed around:

js
const form = document.querySelector("form");
const submit = form.submit;
submit(); // ❌ Illegal invocation

That’s why browsers throw an “Illegal invocation” error — submit was called without the correct this.

By using an arrow function, you ensure the method is called in the right context:

tsx
onClick={() => formRef.current?.submit()}

Here, submit() runs as a method of formRef.current, keeping its this bound correctly.


When You Could Use a Direct Reference (Rarely)

In some edge cases, you can safely do this:

tsx
onClick={formRef.current?.submit?.bind(formRef.current)}

This ensures this is bound properly — but it only works if the ref is already defined before render, which is rarely the case for React refs tied to DOM nodes.

So in almost all scenarios, the arrow function form is simpler and safer.


TL;DR — Always Wrap Ref Calls in a Function

VersionWhat Happens at RenderWorks?Why
onClick={formRef.current?.submit}Evaluates immediately, likely undefinedRef not ready yet
onClick={() => formRef.current?.submit()}Passes a callable functionEvaluates at click time
onClick={formRef.current.submit.bind(formRef.current)}Works only if ref is defined early⚠️Rarely needed

Key Takeaways

  • React event props like onClick expect a function reference, not the result of a function call.
  • formRef.current is null during the initial render — so direct access fails.
  • Wrapping in an arrow function defers evaluation until the click event, when the ref is ready.
  • Arrow functions also preserve the correct this context for DOM methods like .submit().

Example Fix

tsx
function MyForm() {
  const formRef = React.useRef<HTMLFormElement>(null);

  return (
    <form ref={formRef}>
      <button type="button" onClick={() => formRef.current?.submit()}>
        Submit Form
      </button>
    </form>
  );
}

This is the cleanest and most reliable way to trigger a form submission from a ref in React.


Author’s note: This is one of those little gotchas that reveal how React renders and how JavaScript treats function references. Once you see it, you’ll start recognizing similar patterns everywhere — especially when working with refs and event handlers.

Found this useful?