Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ class Field extends React.Component<InternalFieldProps, FieldState> implements F
const namePath = this.getNamePath();
const currentValue = this.getValue();

const { triggerName, validateOnly = false } = options || {};
const { triggerName, validateOnly = false, delayFrame: showDelayFrame } = options || {};

// Force change to async to avoid rule OOD under renderProps field
const rootPromise = Promise.resolve().then(async (): Promise<any[]> => {
Expand All @@ -404,7 +404,9 @@ class Field extends React.Component<InternalFieldProps, FieldState> implements F

// Should wait for the frame render,
// since developer may `useWatch` value in the rules.
await delayFrame();
if (showDelayFrame) {
await delayFrame();
}
Comment on lines +407 to +409

Choose a reason for hiding this comment

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

high

This change makes the delayFrame conditional. While this is useful for adding the new option, it changes the default behavior. Previously, await delayFrame() was called unconditionally, which helped prevent race conditions with useWatch in validation rules.

Now, the delay only occurs if delayFrame: true is explicitly passed. This removes the delay for standard validation triggers (like onChange) and manual form.validateFields() calls, which could be a regression for users relying on this behavior (e.g., calling validateFields immediately after a state change that useWatch depends on).

If the goal is to allow opting out of the delay for performance, rather than requiring an opt-in, I'd suggest keeping the delay by default to avoid a breaking change. This can be done by checking if showDelayFrame is not explicitly false.

This would maintain backward compatibility while still providing a path for performance optimization.

Suggested change
if (showDelayFrame) {
await delayFrame();
}
if (showDelayFrame !== false) {
await delayFrame();
}


// Start validate
let filteredRules = this.getRules();
Expand Down
5 changes: 4 additions & 1 deletion src/hooks/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,10 @@ export class FormStore {
private triggerDependenciesUpdate = (prevStore: Store, namePath: InternalNamePath) => {
const childrenFields = this.getDependencyChildrenFields(namePath);
if (childrenFields.length) {
this.validateFields(childrenFields);
this.validateFields(childrenFields, {
// Delay to avoid `useWatch` dynamic adjust rules that deps not get latest one
delayFrame: true,
});
}

this.notifyObservers(prevStore, childrenFields, {
Expand Down
1 change: 1 addition & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export type ValidateFields<Values = any> = {
export interface InternalValidateOptions extends ValidateOptions {
triggerName?: string;
validateMessages?: ValidateMessages;
delayFrame?: boolean;
}

export type InternalValidateFields<Values = any> = {
Expand Down
2 changes: 0 additions & 2 deletions tests/context.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import InfoField from './common/InfoField';
import { changeValue, matchError, getInput } from './common';
import timeout from './common/timeout';

jest.mock('../src/utils/delayUtil');

describe('Form.Context', () => {
it('validateMessages', async () => {
const { container } = render(
Expand Down
2 changes: 0 additions & 2 deletions tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import { changeValue, getInput, matchError } from './common';
import InfoField, { Input } from './common/InfoField';
import timeout, { waitFakeTime } from './common/timeout';

jest.mock('../src/utils/delayUtil');
import type { FormRef, Meta } from '@/interface';

describe('Form.Basic', () => {
describe('create form', () => {
const Content: React.FC = () => (
Expand Down
2 changes: 0 additions & 2 deletions tests/list.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import InfoField, { Input } from './common/InfoField';
import { changeValue, getInput } from './common';
import timeout from './common/timeout';

jest.mock('../src/utils/delayUtil');

describe('Form.List', () => {
const form = React.createRef<FormInstance>();

Expand Down
2 changes: 0 additions & 2 deletions tests/validate-warning.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { changeValue, getInput, matchError } from './common';
import type { FormInstance, Rule } from '../src/interface';
import { render } from '@testing-library/react';

jest.mock('../src/utils/delayUtil');

describe('Form.WarningValidate', () => {
it('required', async () => {
const form = React.createRef<FormInstance>();
Expand Down