Skip to content

Conversation

@a28689604
Copy link
Contributor

@a28689604 a28689604 commented Nov 9, 2025

Proposed Changes

Problem:

#13129
When using useFieldArray.replace to update data, the performance becomes very slow and can cause the browser to freeze.
This happens because the entire form validation resolver is triggered for every single replaced field.

Root cause flow:

  1. replace is called (it updates _formValues, sets fields, and calls _setFieldArray).

    const replace = (
    value:
    | Partial<FieldArray<TFieldValues, TFieldArrayName>>
    | Partial<FieldArray<TFieldValues, TFieldArrayName>>[],
    ) => {
    const updatedFieldArrayValues = convertToArrayPayload(cloneObject(value));
    ids.current = updatedFieldArrayValues.map(generateId);
    updateValues([...updatedFieldArrayValues]);
    setFields([...updatedFieldArrayValues]);
    control._setFieldArray(
    name,
    [...updatedFieldArrayValues],
    <T>(data: T): T => data,
    {},
    true,
    false,
    );
    };

  2. React re-renders because the fields have changed; during rendering, the JSX executes register(name) for each input.

  3. In every single field new registration, updateValidAndValue is executed, which internally calls setValid .

    if (field) {
    _setDisabledField({
    disabled: isBoolean(options.disabled)
    ? options.disabled
    : _options.disabled,
    name,
    });
    } else {
    updateValidAndValue(name, true, options.value);
    }

    _state.mount && _setValid();

  4. setValid always triggers _runSchema() without any arguments, which means it validates the entire form.
    This is the root cause of the performance issue, the whole form is being validated for every newly registered field.

    const isValid = _options.resolver
    ? isEmptyObject((await _runSchema()).errors)
    : await executeBuiltInValidation(_fields, true);

Please refer to the following CodeSandbox example, which is based on the one from issue #13129.
It now logs how many times the resolver is called.
Open the console, click add data, and wait for the execution, this demonstrates that the form validation resolver runs once per field, confirming the issue.
https://codesandbox.io/p/devbox/reslover-isvalid-issue-forked-thzpcv

Fix:

When replace calls _setFieldArray, it raises a flag by setting _state.action to true, and only resets it to false inside a useEffect, meaning this happens after the changes are committed.

_state.action = true;

control._state.action = false;

The register executions triggered by the re-render occur before the commit, so if we add an _state.action check inside the _setValid() call within updateValidAndValue, we can safely skip the validator call during field updates.

This doesn’t mean the validator is skipped entirely, at the end of the useEffect, _setValid() is called again, ensuring the form is always validated with the resolver once the update is complete.

control._setValid();

Fixes #13129

Performance Benchmark

The table below compares the execution time before and after the optimization when replacing a batch of fields.

Fields (size) Before After Improvement
50 (mid) 14 s 0.045 s ~300× faster
100 (large) 84 s 0.087 s ~965× faster

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing tests pass locally with my changes

@a28689604 a28689604 force-pushed the chore/skip-setValid-during-batch-array branch from 8e45115 to 4888e3b Compare November 9, 2025 18:32
@bluebill1049
Copy link
Member

could you include some test coverage on this change plz?

Add tests to ensure resolver is invoked correctly during append, insert, prepend, replace, and update operations
@a28689604
Copy link
Contributor Author

a28689604 commented Nov 10, 2025

could you include some test coverage on this change plz?

Hi, I’ve added tests for the useFieldArray actions that involve re-registering fields. The tests verify that the resolver is called the expected number of times. Thanks!

Copy link
Member

@bluebill1049 bluebill1049 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one. thanks!

@bluebill1049 bluebill1049 merged commit 932c957 into react-hook-form:master Nov 10, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

issue: slow performance of fields (300+ inputs) when reading formState

2 participants