Skip to content

Commit

Permalink
πŸ‘―β€β™‚οΈ support async defaultValues and form values update (#9261)
Browse files Browse the repository at this point in the history
* auto reset form values with reactive values prop

* remove redundant set

* update API

* check for object instead of null/undefined

* remove redundant Object.is and isObject check

* 😞increase bundle check size

* rename from `resetValuesOptions` to `resetOptions`

* update API extrator

* update with test cases

* remove extra sleep function

* remove useCallback

* deps package update

* fix build

* Revert "fix build"

This reverts commit a3e78d0.

* Revert "deps package update"
asdasdahhhhhkl

:
This reverts commit bcbb120.

* support async defaultValues

* fix defaultValues as default state

* fix type

* update API extrator

* add missing type test

* save bytes

* brought change from main

* 7.40.0-next.0

* improve resetOptions with keepDirtyValues without subscription to formState

* keep update dirty fields internally without subscription

* update type for AsyncDefaultValues

* update api extrator

* update packages

* infer type from async defaultValues (#9387)

* infer type from async defaultValues

* fix type cast

* update api extractor

* Update src/types/utils.ts

Co-authored-by: Felix Schorer <felixschorer@users.noreply.github.com>

* Update src/types/form.ts

Co-authored-by: Felix Schorer <felixschorer@users.noreply.github.com>

* update api extrator

Co-authored-by: Felix Schorer <felixschorer@users.noreply.github.com>

* 7.40.0-next.1

* fix UnPackDefaultValues at path level and rename tsd to test:type

* Revert "fix UnPackDefaultValues at path level and rename tsd to test:type"

This reverts commit 362f369.

* fix array field path

* fix api extrator

* - fix touched, dirty field state promise infer
- change tsd script to run test:type
- added more type test coverage for promise infer

* - rename to UnPackAsyncDefaultValues
- fix build

* - fix test script for workflow
- update api extrator

* update packages

* type test coverage for each hooks

* resolve conflict from master branch

* - fix test case
- fix import * as react

* move getValidationModes to logic folder

* fix import

* revert change on useWatch

* - update test case to include controlled component
- remove inline sleep function

Co-authored-by: Felix Schorer <felixschorer@users.noreply.github.com>
  • Loading branch information
bluebill1049 and felixschorer committed Dec 4, 2022
1 parent cb666b8 commit fcd7985
Show file tree
Hide file tree
Showing 20 changed files with 1,637 additions and 1,859 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-test.yml
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Test
run: |
yarn test --ci
yarn tsd
yarn test:type
- name: Bundle watch
run: |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -42,7 +42,7 @@
"test:web": "TEST_ENV=web yarn test",
"test:server": "TEST_ENV=server yarn test",
"test:native": "TEST_ENV=native yarn test",
"tsd": "tsd src/__typetest__",
"test:type": "tsd src/__typetest__",
"e2e": "cypress run",
"e2e:watch": "cypress open",
"api-extractor": "api-extractor run --local",
Expand Down
31 changes: 19 additions & 12 deletions reports/api-extractor.md
Expand Up @@ -54,6 +54,7 @@ export type Control<TFieldValues extends FieldValues = FieldValues, TContext = a
action: boolean;
watch: boolean;
};
_reset: UseFormReset<TFieldValues>;
_options: UseFormProps<TFieldValues, TContext>;
_getDirty: GetIsDirty;
_formState: FormState<TFieldValues>;
Expand All @@ -74,7 +75,7 @@ export type Control<TFieldValues extends FieldValues = FieldValues, TContext = a
};

// @public
export const Controller: <TFieldValues extends FieldValues = FieldValues, TName extends Path<TFieldValues> = Path<TFieldValues>>(props: ControllerProps<TFieldValues, TName>) => ReactElement<any, string | JSXElementConstructor<any>>;
export const Controller: <TFieldValues extends FieldValues = FieldValues, TName extends Path<UnPackAsyncDefaultValues<TFieldValues>> = Path<UnPackAsyncDefaultValues<TFieldValues>>>(props: ControllerProps<TFieldValues, TName>) => ReactElement<any, string | JSXElementConstructor<any>>;

// @public (undocumented)
export type ControllerFieldState = {
Expand Down Expand Up @@ -181,10 +182,10 @@ export type FieldArrayMethodProps = {
};

// @public
export type FieldArrayPath<TFieldValues extends FieldValues> = ArrayPath<TFieldValues>;
export type FieldArrayPath<TFieldValues extends FieldValues> = ArrayPath<UnPackAsyncDefaultValues<TFieldValues>>;

// @public
export type FieldArrayPathValue<TFieldValues extends FieldValues, TFieldArrayPath extends FieldArrayPath<TFieldValues>> = PathValue<TFieldValues, TFieldArrayPath>;
export type FieldArrayPathValue<TFieldValues extends FieldValues, TFieldArrayPath extends FieldArrayPath<TFieldValues>> = PathValue<UnPackAsyncDefaultValues<TFieldValues>, TFieldArrayPath>;

// @public
export type FieldArrayWithId<TFieldValues extends FieldValues = FieldValues, TFieldArrayName extends FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>, TKeyName extends string = 'id'> = FieldArray<TFieldValues, TFieldArrayName> & Record<TKeyName, string>;
Expand All @@ -202,7 +203,7 @@ export type FieldError = {
};

// @public (undocumented)
export type FieldErrors<T extends FieldValues = FieldValues> = Partial<FieldValues extends IsAny<FieldValues> ? any : FieldErrorsImpl<DeepRequired<T>>>;
export type FieldErrors<T extends FieldValues = FieldValues> = Partial<FieldValues extends IsAny<FieldValues> ? any : FieldErrorsImpl<DeepRequired<UnPackAsyncDefaultValues<T>>>>;

// @public (undocumented)
export type FieldErrorsImpl<T extends FieldValues = FieldValues> = {
Expand All @@ -216,15 +217,15 @@ export type FieldName<TFieldValues extends FieldValues> = IsFlatObject<TFieldVal
export type FieldNamesMarkedBoolean<TFieldValues extends FieldValues> = DeepMap<DeepPartial<TFieldValues>, boolean>;

// @public
export type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>;
export type FieldPath<TFieldValues extends FieldValues> = Path<UnPackAsyncDefaultValues<TFieldValues>>;

// @public
export type FieldPathByValue<TFieldValues extends FieldValues, TValue> = {
[Key in FieldPath<TFieldValues>]: FieldPathValue<TFieldValues, Key> extends TValue ? Key : never;
}[FieldPath<TFieldValues>];

// @public
export type FieldPathValue<TFieldValues extends FieldValues, TFieldPath extends FieldPath<TFieldValues>> = PathValue<TFieldValues, TFieldPath>;
export type FieldPathValue<TFieldValues extends FieldValues, TFieldPath extends FieldPath<TFieldValues>> = PathValue<UnPackAsyncDefaultValues<TFieldValues>, TFieldPath>;

// @public
export type FieldPathValues<TFieldValues extends FieldValues, TPath extends FieldPath<TFieldValues>[] | readonly FieldPath<TFieldValues>[]> = {} & {
Expand Down Expand Up @@ -257,9 +258,9 @@ export type FormState<TFieldValues extends FieldValues> = {
isValidating: boolean;
isValid: boolean;
submitCount: number;
defaultValues?: Readonly<DeepPartial<TFieldValues>> | TFieldValues;
dirtyFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>;
touchedFields: Partial<Readonly<FieldNamesMarkedBoolean<TFieldValues>>>;
defaultValues?: UnPackAsyncDefaultValues<TFieldValues> | undefined | Readonly<DeepPartial<TFieldValues>>;
dirtyFields: Partial<Readonly<FieldNamesMarkedBoolean<UnPackAsyncDefaultValues<TFieldValues>>>>;
touchedFields: Partial<Readonly<FieldNamesMarkedBoolean<UnPackAsyncDefaultValues<TFieldValues>>>>;
errors: FieldErrors<TFieldValues>;
};

Expand Down Expand Up @@ -486,6 +487,9 @@ export type TriggerConfig = Partial<{
shouldFocus: boolean;
}>;

// @public (undocumented)
export type UnPackAsyncDefaultValues<TFieldValues> = TFieldValues extends () => Promise<infer U> ? U : TFieldValues;

// @public @deprecated (undocumented)
export type UnpackNestedValue<T> = T extends NestedValue<infer U> ? U : T extends Date | FileList | File | Blob ? T : T extends object ? {
[K in keyof T]: UnpackNestedValue<T[K]>;
Expand Down Expand Up @@ -589,10 +593,12 @@ export type UseFormGetValues<TFieldValues extends FieldValues> = {
export type UseFormHandleSubmit<TFieldValues extends FieldValues> = (onValid: SubmitHandler<TFieldValues>, onInvalid?: SubmitErrorHandler<TFieldValues>) => (e?: React_2.BaseSyntheticEvent) => Promise<void>;

// @public (undocumented)
export type UseFormProps<TFieldValues extends FieldValues = FieldValues, TContext = any> = Partial<{
export type UseFormProps<TFieldValues extends UnPackAsyncDefaultValues<FieldValues> = UnPackAsyncDefaultValues<FieldValues>, TContext = any> = Partial<{
mode: Mode;
reValidateMode: Exclude<Mode, 'onTouched' | 'all'>;
defaultValues: DefaultValues<TFieldValues>;
defaultValues: DefaultValues<TFieldValues> | AsyncDefaultValues<TFieldValues>;
values: TFieldValues;
resetOptions: Parameters<UseFormReset<TFieldValues>>[1];
resolver: Resolver<TFieldValues, TContext>;
context: TContext;
shouldFocusError: boolean;
Expand Down Expand Up @@ -771,7 +777,8 @@ export type WatchObserver<TFieldValues extends FieldValues> = (value: DeepPartia

// Warnings were encountered during analysis:
//
// src/types/form.ts:418:3 - (ae-forgotten-export) The symbol "Subscription" needs to be exported by the entry point index.d.ts
// src/types/form.ts:97:3 - (ae-forgotten-export) The symbol "AsyncDefaultValues" needs to be exported by the entry point index.d.ts
// src/types/form.ts:431: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
@@ -1,4 +1,4 @@
import isPlainObject from './isPlainObject';
import isPlainObject from '../utils/isPlainObject';

describe('isPlainObject', function () {
it('should identify plan object or not', function () {
Expand All @@ -12,11 +12,11 @@ describe('isPlainObject', function () {
expect(isPlainObject(Object.create(Object.prototype))).toBeTruthy();
expect(isPlainObject({ foo: 'bar' })).toBeTruthy();
expect(isPlainObject({})).toBeTruthy();
expect(isPlainObject(Object.create(null))).toBeTruthy();
expect(!isPlainObject(/foo/)).toBeFalsy();
expect(!isPlainObject(function () {})).toBeFalsy();
expect(!isPlainObject(['foo', 'bar'])).toBeFalsy();
expect(!isPlainObject([])).toBeFalsy();
expect(!isPlainObject(test)).toBeFalsy();
expect(isPlainObject(Object.create(null))).toBeFalsy();
expect(!isPlainObject(/foo/)).toBeTruthy();
expect(!isPlainObject(function () {})).toBeTruthy();
expect(!isPlainObject(['foo', 'bar'])).toBeTruthy();
expect(!isPlainObject([])).toBeTruthy();
expect(!isPlainObject(test)).toBeTruthy();
});
});
128 changes: 128 additions & 0 deletions src/__tests__/type.test.tsx
Expand Up @@ -6,10 +6,13 @@ import {
FieldPath,
FieldValues,
Path,
PathValue,
UseFormRegister,
} from '../types';
import { useController } from '../useController';
import { useFieldArray } from '../useFieldArray';
import { useForm } from '../useForm';
import { useFormState } from '../useFormState';
import { useWatch } from '../useWatch';

test('should not throw type error with path name', () => {
Expand Down Expand Up @@ -265,13 +268,127 @@ test('should support nullable field errors', () => {
errors;
});

test('should work with generic component path assertion', () => {
function App<T extends FieldValues>() {
const { register } = useForm<T>();
const FIELD_DATA_EXTENSION = '__data';
const item = {
value: 'data',
};

register(`FieldName${FIELD_DATA_EXTENSION}` as FieldPath<T>, {
value: item as PathValue<T, Path<T>>,
});

return null;
}

App;
});

test('should infer async default values', () => {
function App() {
const {
register,
control,
formState,
setValue,
reset,
watch,
getValues,
getFieldState,
clearErrors,
unregister,
setFocus,
trigger,
setError,
} = useForm({
defaultValues: async () => {
return {
test: 'test',
test1: {
nested: 'test',
},
fieldArray: [{ test: '' }],
};
},
});
useFieldArray({
name: 'fieldArray' as const,
control,
});
useController({
name: 'test1.nested',
control,
});
useWatch({
name: 'test1',
control,
});
useFormState({
name: 'fieldArray',
control,
});

setValue('test', 'data');
setValue('test1.nested', 'data');
reset({
test: 'test',
test1: 'test1',
});

watch('test');
watch('test1.nested');

getValues('test');
getValues('test1.nested');

getFieldState('test');
getFieldState('test1.nested');

clearErrors('test');
clearErrors('test1.nested');

unregister('test');
unregister('test1.nested');

setFocus('test');
setFocus('test1.nested');

trigger('test');
trigger('test1.nested');

setError('test', { type: 'test ' });
setError('test1.nested', { type: 'test ' });

return (
<form>
<input {...register('test')} />
<Controller render={() => <input />} name={'test1'} control={control} />
<p>{formState.errors?.test?.message}</p>
<p>{formState.errors?.test1?.message}</p>
<p>{formState.touchedFields.test}</p>
<p>{formState.touchedFields.test1?.nested}</p>
<p>{formState.dirtyFields.test}</p>
<p>{formState.dirtyFields.test1?.nested}</p>
</form>
);
}

App;
});

test('should provide correct type for validate function with useFieldArray', () => {
const App = () => {
const { control } = useForm<{
test: {
first: string;
last: string;
}[];
test1: {
first: string;
last: string;
}[];
}>({
defaultValues: {
test: [
Expand All @@ -291,6 +408,17 @@ test('should provide correct type for validate function with useFieldArray', ()
},
},
});
useFieldArray({
control,
name: 'test1',
rules: {
validate: {
test: (data) => {
return !!data.find((test) => test.first && test.last);
},
},
},
});

return null;
};
Expand Down

0 comments on commit fcd7985

Please sign in to comment.