Skip to content

Commit

Permalink
💬 add isValidating and validatingFields to field state (react-hoo…
Browse files Browse the repository at this point in the history
…k-form#10657)

* Add isValidating to field state

* Fix type tests for isValidating field state

* Adjust bundlewatch maxSize

* Fix api-extractor.md merge fail

* Adjust formState validatingFields property

* Adjust bundlewatch maxSize

* Refactoring for isValidating fieldState

* Fix multi async validators behavior

* Remove unnecessary explicit type definition in isValidating test

* Improve _updateIsValidating method

* Add proxyFormState check to updateIsValidating method

---------

Co-authored-by: Beier (Bill) <bluebill1049@hotmail.com>
  • Loading branch information
2 people authored and rafaelcalhau committed May 5, 2024
1 parent 54f7560 commit 08d73e7
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 79 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -118,7 +118,7 @@
"files": [
{
"path": "./dist/index.cjs.js",
"maxSize": "10 kB"
"maxSize": "10.1 kB"
}
]
},
Expand Down
7 changes: 6 additions & 1 deletion reports/api-extractor.md
Expand Up @@ -91,6 +91,7 @@ export type ControllerFieldState = {
invalid: boolean;
isTouched: boolean;
isDirty: boolean;
isValidating: boolean;
error?: FieldError;
};

Expand Down Expand Up @@ -307,6 +308,7 @@ export type FormState<TFieldValues extends FieldValues> = {
defaultValues?: undefined | Readonly<DeepPartial<TFieldValues>>;
dirtyFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>;
touchedFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>;
validatingFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>;
errors: FieldErrors<TFieldValues>;
};

Expand All @@ -316,6 +318,7 @@ export type FormStateProxy<TFieldValues extends FieldValues = FieldValues> = {
isValidating: boolean;
dirtyFields: FieldNamesMarkedBoolean<TFieldValues>;
touchedFields: FieldNamesMarkedBoolean<TFieldValues>;
validatingFields: FieldNamesMarkedBoolean<TFieldValues>;
errors: boolean;
isValid: boolean;
};
Expand Down Expand Up @@ -384,6 +387,7 @@ export type KeepStateOptions = Partial<{
keepIsSubmitted: boolean;
keepIsSubmitSuccessful: boolean;
keepTouched: boolean;
keepIsValidating: boolean;
keepIsValid: boolean;
keepSubmitCount: boolean;
}>;
Expand Down Expand Up @@ -660,6 +664,7 @@ export type UseFormGetFieldState<TFieldValues extends FieldValues> = <TFieldName
invalid: boolean;
isDirty: boolean;
isTouched: boolean;
isValidating: boolean;
error?: FieldError;
};

Expand Down Expand Up @@ -866,7 +871,7 @@ export type WatchObserver<TFieldValues extends FieldValues> = (value: DeepPartia

// Warnings were encountered during analysis:
//
// src/types/form.ts:440:3 - (ae-forgotten-export) The symbol "Subscription" needs to be exported by the entry point index.d.ts
// src/types/form.ts:444:3 - (ae-forgotten-export) The symbol "Subscription" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
58 changes: 57 additions & 1 deletion src/__tests__/controller.test.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import {
act as actComponent,
fireEvent,
render,
screen,
Expand All @@ -8,7 +9,7 @@ import {
} from '@testing-library/react';

import { Controller } from '../controller';
import { ControllerRenderProps, FieldValues } from '../types';
import { ControllerRenderProps, FieldValues, ValidateResult } from '../types';
import { useFieldArray } from '../useFieldArray';
import { useForm } from '../useForm';
import { FormProvider } from '../useFormContext';
Expand Down Expand Up @@ -261,6 +262,61 @@ describe('Controller', () => {
expect(touched).toEqual({ test: true });
});

it('should set field to formState validatingFields and render field isValidating state', async () => {
jest.useFakeTimers();

const getValidateMock: (timeout: number) => Promise<ValidateResult> = (
timeout: number,
) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, timeout);
});
};

let validatingFields: any;
const Component = () => {
const { control, formState } = useForm({ mode: 'onBlur' });

validatingFields = formState.validatingFields;

return (
<Controller
defaultValue=""
name="test"
render={({ field, fieldState }) => (
<>
<div>isValidating: {String(fieldState.isValidating)}</div>
<input {...field} />
</>
)}
control={control}
rules={{
validate: () => getValidateMock(1000),
}}
/>
);
};

render(<Component />);

expect(validatingFields).toEqual({});
expect(screen.getByText('isValidating: false')).toBeVisible();

fireEvent.blur(screen.getByRole('textbox'));

expect(validatingFields).toEqual({ test: true });
expect(screen.getByText('isValidating: true')).toBeVisible();

await actComponent(async () => {
jest.advanceTimersByTime(1100);
});

expect(validatingFields).toEqual({ test: false });
expect(screen.getByText('isValidating: false')).toBeVisible();
});

it('should call trigger method when re-validate mode is onBlur with blur event', async () => {
const Component = () => {
const {
Expand Down

0 comments on commit 08d73e7

Please sign in to comment.