TanStack / TanStack/form

isSubmitting spinner not visible before onSubmitAsync runs (browser paint timing issue)

Open
#1,967 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

v1 v2: needs investigation
Dominant language
TypeScript
Stars
6.7k
Forks
682
Avg merge
5d 18h
Merged PRs (30d)
7

Description

Description

When using onSubmitAsync for form validation, the isSubmitting state is set to true correctly, but the browser doesn't have time to paint the updated UI (e.g., a loading spinner) before the async validator function starts executing.

Steps to Reproduce

  1. Create a form with onSubmitAsync validator
  2. Subscribe to isSubmitting to show a spinner
  3. In onSubmitAsync, immediately call an async operation (e.g., API validation)
const form = useForm({
  validators: {
    onSubmitAsync: async ({ value }) => {
      // Browser hasn't painted isSubmitting=true spinner yet!
      const isValid = await validateApiKey(value.apiKey)
      return isValid ? undefined : { form: 'Invalid API key' }
    },
  },
})

// In JSX:
<form.Subscribe selector={(state) => state.isSubmitting}>
  {(isSubmitting) => (
    <Button disabled={isSubmitting}>
      {isSubmitting ? <Spinner /> : 'Submit'}
    </Button>
  )}
</form.Subscribe>

Expected Behavior

The spinner should be visible immediately when the user clicks submit, before onSubmitAsync starts executing.

Actual Behavior

The spinner appears late (or not at all for fast operations) because:

  1. handleSubmit() sets isSubmitting = true
  2. React re-renders (virtual DOM updated)
  3. onSubmitAsync is called synchronously — browser hasn't painted yet!
  4. Only after the first await in onSubmitAsync does the browser get a chance to paint

Root Cause

TanStack Form calls onSubmitAsync synchronously after updating state, without yielding to the browser's paint cycle. React's state update schedules a re-render, but the actual browser paint happens asynchronously. By the time the browser paints, the async operation may already be in progress.

Workaround

Use a double requestAnimationFrame at the start of onSubmitAsync to ensure the browser has painted:

onSubmitAsync: async ({ value }) => {
  // Wait for browser to complete paint cycle
  await new Promise((resolve) => {
    requestAnimationFrame(() => {
      requestAnimationFrame(resolve)
    })
  })
  
  // Now the spinner is visible
  const isValid = await validateApiKey(value.apiKey)
  return isValid ? undefined : { form: 'Invalid API key' }
}

Suggested Fix

TanStack Form could yield to the browser's paint cycle after setting isSubmitting = true and before calling async validators. This could be done with:

// Before calling onSubmitAsync
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))

Or alternatively:

await new Promise(resolve => setTimeout(resolve, 0))

Environment

  • @tanstack/react-form: latest
  • React: 18.x / 19.x
  • Browser: Chrome, Safari, Firefox (issue is consistent across browsers)
  • Also reproduced in Tauri WebView

Related Discussions

  • #952 - "What is the purpose of isSubmitting in form state?"
  • Multiple users on Discord/AnswerOverflow reporting "isSubmitting is driving people crazy"

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the form submission path around handleSubmit and the point where isSubmitting is set before onSubmitAsync runs. Reproduce the React example with a spinner and an async validator, then verify that the spinner paints before validation begins without requiring the workaround described in the issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.