Skip to content

Commit

Permalink
Merge branch 'next' into close-#9524
Browse files Browse the repository at this point in the history
  • Loading branch information
bluebill1049 committed Dec 18, 2022
2 parents fb49f73 + 09d1423 commit 0a9a882
Show file tree
Hide file tree
Showing 16 changed files with 232 additions and 65 deletions.
2 changes: 1 addition & 1 deletion examples/V7/parseFormatInputValues.tsx
Expand Up @@ -24,7 +24,7 @@ export default function App() {
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="emails"
as={ParseFormatTextarea}
render={ParseFormatTextarea}
control={control}
defaultValue={[]}
/>
Expand Down
16 changes: 16 additions & 0 deletions src/__tests__/logic/validateField.test.tsx
Expand Up @@ -57,6 +57,22 @@ describe('validateField', () => {
},
});

expect(
await validateField(
{
_f: {
valueAsNumber: true,
mount: true,
name: 'test',
ref: { name: 'test' },
required: 'required',
},
},
2,
false,
),
).toEqual({});

expect(
await validateField(
{
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/type.test.tsx
Expand Up @@ -60,7 +60,7 @@ test('should not throw type error with optional array fields', () => {

{fields.map((field, index) => (
<div key={field.id}>
<input {...register(`things.${index}.name`)} />
<input {...register(`things.${index}.name` as const)} />
</div>
))}
{fieldArray.fields.map((item) => {
Expand Down
17 changes: 14 additions & 3 deletions src/__tests__/useForm.test.tsx
Expand Up @@ -1553,15 +1553,14 @@ describe('useForm', () => {
mode: 'onChange',
resolver: async (values) => {
if (!values.test) {
const result = {
return {
values: {},
errors: {
test: {
type: 'required',
},
},
};
return result;
}

return {
Expand Down Expand Up @@ -1720,7 +1719,10 @@ describe('useForm', () => {

it('should update defaultValues async', async () => {
const App = () => {
const { register } = useForm({
const {
register,
formState: { isLoading },
} = useForm({
defaultValues: async () => {
await sleep(100);

Expand All @@ -1733,17 +1735,26 @@ describe('useForm', () => {
return (
<form>
<input {...register('test')} />
<p>{isLoading ? 'loading...' : 'done'}</p>
</form>
);
};

render(<App />);

await waitFor(() => {
screen.getByText('loading...');
});

await waitFor(() => {
expect((screen.getByRole('textbox') as HTMLInputElement).value).toEqual(
'test',
);
});

await waitFor(() => {
screen.getByText('done');
});
});

it('should update async default values for controlled components', async () => {
Expand Down
29 changes: 29 additions & 0 deletions src/__tests__/useWatch.test.tsx
Expand Up @@ -92,6 +92,35 @@ describe('useWatch', () => {
expect(result.current).toEqual(['test', 'test1']);
});

it('should return own default value for single input', () => {
const { result } = renderHook(() => {
const { control } = useForm<{ test: string; test1: string }>({});
return useWatch({
control,
name: 'test',
defaultValue: 'test',
});
});

expect(result.current).toEqual('test');
});

it('should return own default value for array of inputs', () => {
const { result } = renderHook(() => {
const { control } = useForm<{ test: string; test1: string }>({});
return useWatch({
control,
name: ['test', 'test1'],
defaultValue: {
test: 'test',
test1: 'test1',
},
});
});

expect(result.current).toEqual(['test', 'test1']);
});

it('should return default value when name is undefined', () => {
const { result } = renderHook(() => {
const { control } = useForm<{ test: string; test1: string }>({
Expand Down
38 changes: 38 additions & 0 deletions src/__typetest__/path/eager.test-d.ts
Expand Up @@ -23,6 +23,25 @@ import { _, Depth3Type } from '../__fixtures__';
const actual = _ as Path<{ foo: string[] }>;
expectType<'foo' | `foo.${number}`>(actual);
}

/** it should be able to avoid self-referencing/recursion, not crashing on self-referencing types. */ {
type Foo = { foo: Foo };
const actual = _ as Path<Foo>;
expectType<'foo'>(actual);
}

/** it should not erroneously match subtypes as traversed */ {
type Foo =
| {
foo?: Foo;
bar?: {
baz: 1;
};
}
| {};
const actual = _ as Path<Foo>;
expectType<'foo' | 'bar' | 'bar.baz'>(actual);
}
}

/** {@link ArrayPath} */ {
Expand All @@ -42,6 +61,25 @@ import { _, Depth3Type } from '../__fixtures__';
const actual = _ as ArrayPath<{ foo: string[][][] }>;
expectType<'foo' | `foo.${number}`>(actual);
}

/** it should be able to avoid self-referencing/recursion, not crashing on self-referencing types. */ {
type Foo = { foo: Foo[] };
const actual = _ as ArrayPath<Foo>;
expectType<'foo'>(actual);
}

/** it should not erroneously match subtypes as traversed */ {
type Foo =
| {
bar?: {
baz?: 1;
fooArr?: Foo[];
};
}
| {};
const actual = _ as ArrayPath<Foo>;
expectType<'bar.fooArr'>(actual);
}
}

/** {@link PathValue} */ {
Expand Down
4 changes: 4 additions & 0 deletions src/logic/createFormControl.ts
Expand Up @@ -104,6 +104,7 @@ export function createFormControl<
let _formState: FormState<TFieldValues> = {
submitCount: 0,
isDirty: false,
isLoading: true,
isValidating: false,
isSubmitted: false,
isSubmitting: false,
Expand Down Expand Up @@ -1245,6 +1246,9 @@ export function createFormControl<
if (isFunction(_options.defaultValues)) {
_options.defaultValues().then((values) => {
reset(values, _options.resetOptions);
_subjects.state.next({
isLoading: false,
});
});
}

Expand Down
5 changes: 4 additions & 1 deletion src/logic/validateField.ts
Expand Up @@ -17,6 +17,7 @@ import isObject from '../utils/isObject';
import isRadioInput from '../utils/isRadioInput';
import isRegex from '../utils/isRegex';
import isString from '../utils/isString';
import isUndefined from '../utils/isUndefined';

import appendErrors from './appendErrors';
import getCheckboxValue from './getCheckboxValue';
Expand Down Expand Up @@ -61,7 +62,9 @@ export default async <T extends NativeFieldValue>(
const isCheckBox = isCheckBoxInput(ref);
const isRadioOrCheckbox = isRadio || isCheckBox;
const isEmpty =
((valueAsNumber || isFileInput(ref)) && !ref.value) ||
((valueAsNumber || isFileInput(ref)) &&
isUndefined(ref.value) &&
isUndefined(inputValue)) ||
inputValue === '' ||
(Array.isArray(inputValue) && !inputValue.length);
const appendErrorsCurry = appendErrors.bind(
Expand Down
1 change: 1 addition & 0 deletions src/types/form.ts
Expand Up @@ -130,6 +130,7 @@ export type ReadFormState = { [K in keyof FormStateProxy]: boolean | 'all' };

export type FormState<TFieldValues extends FieldValues> = {
isDirty: boolean;
isLoading: boolean;
isSubmitted: boolean;
isSubmitSuccessful: boolean;
isSubmitting: boolean;
Expand Down
105 changes: 80 additions & 25 deletions src/types/path/eager.ts
@@ -1,21 +1,58 @@
import { FieldValues } from '../fields';
import {
BrowserNativeObject,
IsAny,
IsEqual,
Primitive,
UnPackAsyncDefaultValues,
} from '../utils';

import { ArrayKey, IsTuple, TupleKeys } from './common';

/**
* Helper function to break apart T1 and check if any are equal to T2
*
* See {@link IsEqual}
*/
type AnyIsEqual<T1, T2> = T1 extends T2
? IsEqual<T1, T2> extends true
? true
: never
: never;

/**
* Helper type for recursively constructing paths through a type.
* This actually constructs the strings and recurses into nested
* object types.
*
* See {@link Path}
*/
type PathImpl<K extends string | number, V> = V extends
type PathImpl<K extends string | number, V, TraversedTypes> = V extends
| Primitive
| BrowserNativeObject
? `${K}`
: `${K}` | `${K}.${Path<V>}`;
: // Check so that we don't recurse into the same type
// by ensuring that the types are mutually assignable
// mutually required to avoid false positives of subtypes
true extends AnyIsEqual<TraversedTypes, V>
? `${K}`
: `${K}` | `${K}.${PathInternal<V, TraversedTypes | V>}`;

/**
* Helper type for recursively constructing paths through a type.
* This obsucres the internal type param TraversedTypes from exported contract.
*
* See {@link Path}
*/
type PathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V>
? IsTuple<T> extends true
? {
[K in TupleKeys<T>]-?: PathImpl<K & string, T[K], TraversedTypes>;
}[TupleKeys<T>]
: PathImpl<ArrayKey, V, TraversedTypes>
: {
[K in keyof T]-?: PathImpl<K & string, T[K], TraversedTypes>;
}[keyof T];

/**
* Type which eagerly collects all paths through a type
Expand All @@ -25,15 +62,9 @@ type PathImpl<K extends string | number, V> = V extends
* Path<{foo: {bar: string}}> = 'foo' | 'foo.bar'
* ```
*/
export type Path<T> = T extends ReadonlyArray<infer V>
? IsTuple<T> extends true
? {
[K in TupleKeys<T>]-?: PathImpl<K & string, T[K]>;
}[TupleKeys<T>]
: PathImpl<ArrayKey, V>
: {
[K in keyof T]-?: PathImpl<K & string, T[K]>;
}[keyof T];
// We want to explode the union type and process each individually
// so assignable types don't leak onto the stack from the base.
export type Path<T> = T extends any ? PathInternal<T> : never;

/**
* See {@link Path}
Expand All @@ -44,36 +75,60 @@ export type FieldPath<TFieldValues extends FieldValues> = Path<

/**
* Helper type for recursively constructing paths through a type.
* This actually constructs the strings and recurses into nested
* object types.
*
* See {@link ArrayPath}
*/
type ArrayPathImpl<K extends string | number, V> = V extends
type ArrayPathImpl<K extends string | number, V, TraversedTypes> = V extends
| Primitive
| BrowserNativeObject
? never
? IsAny<V> extends true
? string
: never
: V extends ReadonlyArray<infer U>
? U extends Primitive | BrowserNativeObject
? IsAny<V> extends true
? string
: never
: // Check so that we don't recurse into the same type
// by ensuring that the types are mutually assignable
// mutually required to avoid false positives of subtypes
true extends AnyIsEqual<TraversedTypes, V>
? never
: `${K}` | `${K}.${ArrayPath<V>}`
: `${K}.${ArrayPath<V>}`;
: `${K}` | `${K}.${ArrayPathInternal<V, TraversedTypes | V>}`
: true extends AnyIsEqual<TraversedTypes, V>
? never
: `${K}.${ArrayPathInternal<V, TraversedTypes | V>}`;

/**
* Helper type for recursively constructing paths through a type.
* This obsucres the internal type param TraversedTypes from exported contract.
*
* See {@link ArrayPath}
*/
type ArrayPathInternal<T, TraversedTypes = T> = T extends ReadonlyArray<infer V>
? IsTuple<T> extends true
? {
[K in TupleKeys<T>]-?: ArrayPathImpl<K & string, T[K], TraversedTypes>;
}[TupleKeys<T>]
: ArrayPathImpl<ArrayKey, V, TraversedTypes>
: {
[K in keyof T]-?: ArrayPathImpl<K & string, T[K], TraversedTypes>;
}[keyof T];

/**
* Type which eagerly collects all paths through a type which point to an array
* type.
* @typeParam T - type which should be introspected
* @typeParam T - type which should be introspected.
* @example
* ```
* Path<{foo: {bar: string[], baz: number[]}}> = 'foo.bar' | 'foo.baz'
* ```
*/
export type ArrayPath<T> = T extends ReadonlyArray<infer V>
? IsTuple<T> extends true
? {
[K in TupleKeys<T>]-?: ArrayPathImpl<K & string, T[K]>;
}[TupleKeys<T>]
: ArrayPathImpl<ArrayKey, V>
: {
[K in keyof T]-?: ArrayPathImpl<K & string, T[K]>;
}[keyof T];
// We want to explode the union type and process each individually
// so assignable types don't leak onto the stack from the base.
export type ArrayPath<T> = T extends any ? ArrayPathInternal<T> : never;

/**
* See {@link ArrayPath}
Expand Down

0 comments on commit 0a9a882

Please sign in to comment.