Skip to content

TabbedForm with many required (async-wrapped) validators throws "Maximum update depth exceeded" on submit #11290

Description

@imrans110

What you were expecting:

Submitting a TabbedForm with many required fields should validate and show errors — the same way a small form, or a SimpleForm with the identical fields, does.

What happened instead:

Once a TabbedForm has enough required fields spread across several tabs, clicking the default <SaveButton> throws "Maximum update depth exceeded" and the form never submits. The same fields in a SimpleForm submit fine.

Reproduced deterministically (3/3) with a headless browser on react-admin 5.14.5.

Steps to reproduce:

  1. A TabbedForm with ~6 tabs × ~8 required TextInputs (validator defined at module scope — not the inline-validator pitfall), default toolbar / SaveButton.
  2. Open the edit page and click Save with the fields empty.
  3. Maximum update depth exceeded; the form does not submit.
  4. Move the identical fields into a SimpleForm → no crash. Or add a form-level validate to the TabbedForm → no crash.

Related code: (runnable — drop into a fork of examples/simple, or any Vite + react-admin app)

import {
  Admin, Resource, List, Datagrid, TextField, Edit, TabbedForm,
  TextInput, required,
} from "react-admin";
import fakeDataProvider from "ra-data-fakerest";

const validate = required();             // single validator, module scope
const TABS = 6, PER = 8;                 // ~49 required fields across tabs

const PostList = () => (
  <List><Datagrid rowClick="edit"><TextField source="batch" /></Datagrid></List>
);
const PostEdit = () => (
  <Edit>
    <TabbedForm>                          {/* crashes; <SimpleForm> does not */}
      <TabbedForm.Tab label="Info">
        <TextInput source="batch" validate={validate} />
      </TabbedForm.Tab>
      {Array.from({ length: TABS }, (_, n) => (
        <TabbedForm.Tab key={n} label={`Tab ${n + 1}`}>
          {Array.from({ length: PER }, (_, i) => (
            <TextInput key={`t${n}f${i}`} source={`t${n}f${i}`} validate={validate} />
          ))}
        </TabbedForm.Tab>
      ))}
    </TabbedForm>
  </Edit>
);
const dataProvider = fakeDataProvider({ posts: [{ id: 1, batch: "B1" }] });
export const App = () => (
  <Admin dataProvider={dataProvider}>
    <Resource name="posts" list={PostList} edit={PostEdit} />
  </Admin>
);
// Open /posts/1 -> click Save -> "Maximum update depth exceeded".

Minimal isolation — same field count, only the form container changes:

Container Result
TabbedForm (per-tab FormGroup) crash
SimpleForm (no FormGroup) ok
TabbedForm + form-level validate (resolver) ok

Likely mechanism:

  1. useInput wraps every validate as an async function, so react-hook-form's hasPromiseValidation is always true and it toggles validatingFields per field via _updateIsValidating during validation.
  2. <FormTab> wraps content in <FormGroupContextProvider>; useFormGroup subscribes to validatingFields and calls setState on every change.
  3. With enough required async fields across the tab FormGroups, a single submit churns validatingFields field-by-field, and the re-render cascade exceeds React's NESTED_UPDATE_LIMIT (50). SimpleForm has no validatingFields subscriber, so it is immune.

Notes:

  • Threshold/timing-sensitive near the limit — small forms don't trip it, and it can be flaky right at the boundary (consistent with hovering at the 50-update ceiling).
  • Not specific to array-vs-single validators, nor to useWatch/helperText — plain TextInputs with a single required() reproduce it.

This looks like a severe, at-scale variant of #5323 ("Async validation leads to form-level re-rendering"), which is closed and described only extra re-renders, not a crash.

Workaround: provide a single form-level validate, which react-admin turns into a resolver (getSimpleValidationResolver). react-hook-form's handleSubmit runs resolver XOR per-field, so it validates the whole form in one pass and skips per-field validation — validatingFields toggles once in bulk instead of per field, and the cascade never happens.

Environment

  • React-admin version: 5.14.5
  • ra-core version: 5.14.5
  • react-hook-form version: 7.72.1
  • React version: 19.2.5
  • @mui/material version: 7.3.10
  • Last version that did not exhibit the issue (if applicable): not version-specific — inherent to async-wrapped validators in a TabbedForm at scale
  • Browser: Chromium (headless, Playwright) and Chrome

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions