Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add support for generic components using FieldPathWithValue #6562

Merged
merged 21 commits into from
Oct 2, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3674c13
✨ Add support for generic components using FieldPathWithValue
julienfouilhe Sep 15, 2021
2058b5b
✅ Fix tests
julienfouilhe Sep 16, 2021
0ec5243
✨ Fix inference
julienfouilhe Sep 16, 2021
cae4e5a
Revert "✅ Fix tests"
julienfouilhe Sep 16, 2021
1fdf829
🎨 Improve readibility of `FieldPathWithValue`
julienfouilhe Sep 16, 2021
c71740c
Merge branch 'master' into generic-components
bluebill1049 Sep 16, 2021
e2710df
✨ Use TResult for `ControllerFieldState` too
julienfouilhe Sep 16, 2021
c5508fe
✨ Put `UnpackNestedValue` back
julienfouilhe Sep 16, 2021
f3cf1ee
Merge branch 'master' into generic-components
bluebill1049 Sep 18, 2021
35c50f5
Merge branch 'master' into generic-components
bluebill1049 Sep 18, 2021
d98f28a
Merge branch 'master' into generic-components
bluebill1049 Sep 19, 2021
67d1361
Merge branch 'master' into generic-components
bluebill1049 Sep 20, 2021
3995e2f
✅ Add test for generic components with NestedValue
julienfouilhe Sep 22, 2021
d959d59
Merge branch 'master' into generic-components
bluebill1049 Sep 22, 2021
4c4b35d
🐛 Fix issues with new fielderrors
julienfouilhe Sep 22, 2021
9de10fa
Merge remote-tracking branch 'origin/master' into generic-components
julienfouilhe Sep 28, 2021
cbc07d9
✅ Add expectations
julienfouilhe Sep 28, 2021
e7396f1
Merge branch 'master' into generic-components
bluebill1049 Sep 28, 2021
1339ab4
Merge branch 'master' into generic-components
bluebill1049 Sep 30, 2021
4e7c435
Merge branch 'master' into generic-components
bluebill1049 Sep 30, 2021
40d5fd3
Merge branch 'master' into generic-components
bluebill1049 Oct 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 42 additions & 7 deletions src/__tests__/useController.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@testing-library/react';

import { Controller } from '../controller';
import { Control } from '../types';
import { Control, FieldPathWithValue, FieldValues } from '../types';
import { useController } from '../useController';
import { useForm } from '../useForm';

Expand All @@ -32,6 +32,39 @@ describe('useController', () => {
render(<Component />);
});

it('should render generic component correctly', () => {
type ExpectedType = { test: string };

const Generic = <FormValues extends FieldValues>({
name,
control,
}: {
name: FieldPathWithValue<FormValues, ExpectedType>;
control: Control<FormValues>;
}) => {
const {
field: { value },
} = useController<FormValues, ExpectedType>({
name,
control,
defaultValue: { test: 'value' },
});

return <>{value.test}</>;
};

const Component = () => {
const { control } = useForm<{
test: string;
key: ExpectedType[];
}>();

return <Generic name="key.0" control={control} />;
};

render(<Component />);
});

it('should only subscribe to formState at each useController level', async () => {
const renderCounter = [0, 0];
type FormValues = {
Expand Down Expand Up @@ -508,6 +541,12 @@ describe('useController', () => {
});

it('should return defaultValues when component is not yet mounted', async () => {
type FormValues = {
test: {
deep: { test: string; test1: string }[];
};
};

const defaultValues = {
test: {
deep: [
Expand All @@ -520,15 +559,11 @@ describe('useController', () => {
};

const App = () => {
const { control, getValues } = useForm<{
test: {
deep: { test: string; test1: string }[];
};
}>({
const { control, getValues } = useForm<FormValues>({
defaultValues,
});

const { field } = useController({
const { field } = useController<FormValues, string>({
control,
name: 'test.deep.0.test',
});
Expand Down
12 changes: 8 additions & 4 deletions src/controller.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { ControllerProps, FieldPath, FieldValues } from './types';
import { ControllerProps, FieldPathWithValue, FieldValues } from './types';
import { useController } from './useController';

const Controller = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TResult = any,
TName extends FieldPathWithValue<TFieldValues, TResult> = FieldPathWithValue<
TFieldValues,
TResult
>,
>(
props: ControllerProps<TFieldValues, TName>,
) => props.render(useController<TFieldValues, TName>(props));
props: ControllerProps<TFieldValues, TResult, TName>,
) => props.render(useController<TFieldValues, TResult, TName>(props));

export { Controller };
41 changes: 31 additions & 10 deletions src/types/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import {
FieldErrors,
FieldPath,
FieldPathValue,
FieldPathWithValue,
FieldValues,
IsAny,
RefCallBack,
UnpackNestedValue,
julienfouilhe marked this conversation as resolved.
Show resolved Hide resolved
UseFormStateReturn,
} from './';

Expand All @@ -24,49 +25,69 @@ export type ControllerFieldState<

export type ControllerRenderProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TResult = any,
TName extends FieldPathWithValue<TFieldValues, TResult> = FieldPathWithValue<
TFieldValues,
TResult
>,
> = {
onChange: (...event: any[]) => void;
onBlur: () => void;
value: UnpackNestedValue<FieldPathValue<TFieldValues, TName>>;
value: IsAny<TResult> extends true
? FieldPathValue<TFieldValues, TName>
: TResult;
name: TName;
ref: RefCallBack;
};
julienfouilhe marked this conversation as resolved.
Show resolved Hide resolved

export type UseControllerProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TResult = any,
TName extends FieldPathWithValue<TFieldValues, TResult> = FieldPathWithValue<
TFieldValues,
TResult
>,
> = {
name: TName;
rules?: Omit<
RegisterOptions<TFieldValues, TName>,
'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
>;
shouldUnregister?: boolean;
defaultValue?: UnpackNestedValue<FieldPathValue<TFieldValues, TName>>;
defaultValue?: IsAny<TResult> extends true
? FieldPathValue<TFieldValues, TName>
: TResult;
julienfouilhe marked this conversation as resolved.
Show resolved Hide resolved
control?: Control<TFieldValues>;
};

export type UseControllerReturn<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TResult = any,
TName extends FieldPathWithValue<TFieldValues, TResult> = FieldPathWithValue<
TFieldValues,
TResult
>,
> = {
field: ControllerRenderProps<TFieldValues, TName>;
field: ControllerRenderProps<TFieldValues, TResult, TName>;
formState: UseFormStateReturn<TFieldValues>;
fieldState: ControllerFieldState<TFieldValues, TName>;
julienfouilhe marked this conversation as resolved.
Show resolved Hide resolved
};

export type ControllerProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TResult = any,
TName extends FieldPathWithValue<TFieldValues, TResult> = FieldPathWithValue<
TFieldValues,
TResult
>,
> = {
render: ({
field,
fieldState,
formState,
}: {
field: ControllerRenderProps<TFieldValues, TName>;
field: ControllerRenderProps<TFieldValues, TResult, TName>;
fieldState: ControllerFieldState<TFieldValues, TName>;
formState: UseFormStateReturn<TFieldValues>;
}) => React.ReactElement;
} & UseControllerProps<TFieldValues, TName>;
} & UseControllerProps<TFieldValues, TResult, TName>;
10 changes: 10 additions & 0 deletions src/types/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,13 @@ export type UnionLike<T> = [T] extends [Date | FileList | File | NestedValue]
Exclude<UnionKeys<T>, keyof T> | OptionalKeys<T>
>
: T;

export type FieldPathWithValue<
TFieldValues extends FieldValues,
TResult = unknown,
FieldPaths extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
[key in FieldPaths]: FieldPathValue<TFieldValues, key> extends TResult
? key
: never;
}[FieldPaths];
12 changes: 8 additions & 4 deletions src/useController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import get from './utils/get';
import { EVENTS } from './constants';
import {
Field,
FieldPath,
FieldPathWithValue,
FieldValues,
InternalFieldName,
UseControllerProps,
Expand All @@ -17,10 +17,14 @@ import { useFormState } from './useFormState';

export function useController<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
TResult = any,
julienfouilhe marked this conversation as resolved.
Show resolved Hide resolved
TName extends FieldPathWithValue<TFieldValues, TResult> = FieldPathWithValue<
TFieldValues,
TResult
>,
>(
props: UseControllerProps<TFieldValues, TName>,
): UseControllerReturn<TFieldValues, TName> {
props: UseControllerProps<TFieldValues, TResult, TName>,
): UseControllerReturn<TFieldValues, TResult, TName> {
const methods = useFormContext<TFieldValues>();
const { name, control = methods.control, shouldUnregister } = props;
const [value, setInputStateValue] = React.useState(
Expand Down