I have a very similar issue. However, I think your TS definition is "wrong" (same as mine). You define control props as
Control
in
ICheckboxProps
but you pass there
Control<MyFormValues>
. I am struggling to find a way of how to use
useForm<FormInputsType>
with generic form components. I have found few solutions, so far:
I hope there is a way to declare a shared form field component and use it with type-specific form? Can type generic be ignored on
Control
maybe?
error TS2365: Operator '>' cannot be applied to types 'Control<FieldValues>' and '{ defaultValues: { checked: boolean; }; mode: string; }'.
error TS2693: 'FormValues' only refers to a type, but is being used as a value here.
I have been avoiding Option 2 you mentioned because I feel like the syntax is confusing with JSX -- if at all possible I would like to avoid this.
Option 3 fails as mentioned in my original post:
if I don't assign a generic it will just read the object type e.g. Control<{ valuea: string, valueb: boolean }>.
useForm<MyType>
in combination with control={(yourControlInstance as unknown) as Control<FieldValues>}
works for me, its still just a workaround tho.
I agree!
I am not sure if we have the same issue here... if you don't pass generic to useForm
it takes FieldValues
as default and this is the same for Control
type. You can try to define you interface as control: Control<FieldValues>
I hope we will get some support on this :)
Jordan, I was able to get item 1 working. While it is kludgy it will be acceptable for now.
I was unsuccessful not passing a generic to useForm
even with Control<FieldValues>
typing (not just Control
sans generic).
Regarding item 2 (Generics in JSX) I think it would be useful for a more accurately representative example in codesandbox e.g. not containing both components in the same file. My particular implementation is being built for reuse, and so the customized FieldValues
per form will not be able to simply be imported and used by the fields themselves.
Hello !
I'm not quite sure I understand the problem entirely but if the goal is to have a generic component over the type of the useForm
data structure, I think something like this should do the trick:
export interface IReactHookFormFieldProps<T> {
name: FieldPath<T>;
control: Control<T>;
rules?: RegisterOptions<T>;
Here is a sandbox showing it with your code.
For fun I've made another one that ensure that the key passed to the component is a boolean
You can find this version here (If you're interested)
Hope it can be helpful ! :)
twobit, DoctorJohn, alimtunc, TimPetricola, and kevinmamaqi reacted with thumbs up emoji
johndatserakis reacted with heart emoji
All reactions
You define an interface to expect T as generic but then you don't pass it from the component
In this case TS will be able to infer the type of T
based off the props. The only use-case I know about for using
<Component<MyType> {...props} />
is when you when to opt out of TS compiler type inference which here isn't the case.
Here is a great reference about that
it's basically turning off TS check for Control ?
I don't really get what you mean by "turning off TS check".
With the defined interface, TS expect a prop control: Control<T>
, T
being a type that extends Record<string, any>
which is the same constraint as the Control
itself.
Why this issue is closed? It still throws an error.
My LoginFormValues
type is
export type LoginFormValues = {
username: string;
password: string;
My useForm
is
const {
control,
handleSubmit,
formState,
} = useForm<LoginFormValues>({
defaultValues: {
username: '',
password: '',
And my custom input is
export type InputFieldProps = {
control: Control<FieldValues, any>;
So, when i pass control
to the InputField it throws an error above
<InputField
name="username"
size="large"
theme="primary"
color="light"
namespace="login_form"
control={control} // this is marked red
Type 'Control<LoginFormValues, any>' is not assignable to type 'Control<FieldValues, any>'.
The types of '_options.resolver' are incompatible between these types.
Type 'Resolver<LoginFormValues, any>' is not assignable to type 'Resolver<FieldValues, any>'.
This is really frustrating.
@artuska, I'm getting the same error. I think it might have something to do specifically with the use of a resolver as a parameter to useForm
that isn't handled by other generic examples.
My error:
Type 'Control<{ email: string; family_name: string; given_name: string; groups: string[]; username: string; }, any>' is not assignable to type 'Control<FieldValues, any>'.
The types of '_options.resolver' are incompatible between these types.
Type 'Resolver<{ email: string; family_name: string; given_name: string; groups: string[]; username: string; }, any> | undefined' is not assignable to type 'Resolver<FieldValues, any> | undefined'.
Type 'Resolver<{ email: string; family_name: string; given_name: string; groups: string[]; username: string; }, any>' is not assignable to type 'Resolver<FieldValues, any>'.
Here is my code. Interestingly enough there isn't an error with the controller component that for family_name but there is for groups. I wonder if it has to do with the types differences: string vs string[]?
// form.ts
const baseUserSchema = z.object({
email: z.string().email(),
family_name: z.string().min(1, { message: "Family Name is required" }),
given_name: z.string().min(1, { message: "Given Name is required" }),
groups: z.array(z.string()).min(1, { message: "Group is required" }),
username: z.string().min(1, { message: "Username is required" }),
});
type Schema = z.infer<typeof baseUserSchema>;
const { control, handleSubmit, reset, setValue } = useForm<Schema>({
resolver: zodResolver(baseUserSchema),
defaultValues: new CreateCognitoUser(),
});
<SmartTextField
control={control} // no error
data-test="family_name"
label="Family Name"
loading={loadingUser}
name="family_name"
<SmartMultiSelectField
control={control} // error
data-test="groups"
label="Groups"
loading={loadingGroups}
name="groups"
options={groupNameOptions}
// SmartMultiSelectField.tsx
import { ReactElement } from "react";
import { Placeholder } from "@aws-amplify/ui-react";
import { useController, UseControllerProps } from "react-hook-form";
import type { FieldValues } from "react-hook-form";
import { BaseSmartInputProps } from "./baseProps.js";
import { Box, MultiSelectField, MultiSelectFieldProps } from "../index.js";
export interface SmartMultiSelectFieldProps<T>
extends Omit<BaseSmartInputProps<T>, "control" | "name">,
Omit<MultiSelectFieldProps, "name">,
UseControllerProps<T> {}
export function SmartMultiSelectField<T extends FieldValues>(
props: SmartMultiSelectFieldProps<T>
): ReactElement {
const {
control,
errorMessage,
hasError,
label,
loading,
name,
...multiSelectFieldProps
} = props;
const {
field: { ref, onChange, value },
fieldState: { error, invalid },
} = useController({ name, control });
return loading ? (
<Box css={{ display: "flex", flexDirection: "column", gap: "$2" }}>
<label className="amplify-label">{label}</label>
<Placeholder height={40} />
</Box>
) : (
<MultiSelectField
{...(multiSelectFieldProps as Omit<
MultiSelectFieldProps,
"label" | "name"
ref={ref}
errorMessage={errorMessage || error?.message}
hasError={hasError || invalid}
name={name}
label={label}
onChange={onChange}
value={value}
// baseProps.js
import { ReactNode } from "react";
import { Control, FieldValues, FieldPath } from "react-hook-form";
export interface BaseSmartInputProps<T extends FieldValues> {
loading?: boolean;
label: ReactNode;
name: FieldPath<T>;
control: Control<T>;
Hi @bluebill1049 and @bestickley
CSB -> https://codesandbox.io/s/react-hook-form-mui-5-v7-controller-ts-10d64d?file=/src/form/InputField.tsx
I'm also struggling with this error and I ended up using control: any; 😁
Could you take a look how to pass generics in this case ?
Props for MUI components:
type Props = Omit<TextFieldProps, "name"> & {
control: Control<any, object>;
disableErrorSpace?: boolean;
name: string;
maxLength?: number;
const InputField = ({
control,
disableErrorSpace,
name,
required,
label,
maxLength = 50,
...restProps
}: Props) => (
<Controller
render={({ field: { ref, ...restField }, fieldState: { error } }) => (
I saw some issues regarding this topic, but it might be the best place to address the issue.
#8808
#8618
Cheers,
This is how how I typed it:
https://codesandbox.io/s/react-hook-form-mui-5-v7-controller-ts-10d64d?file=/src/form/InputField.tsx
import { Control, Controller, FieldValues, Path } from "react-hook-form";
import { TextField, TextFieldProps } from "@mui/material";
import { textFieldValidator } from "./validators";
type Props<T extends FieldValues> = Omit<TextFieldProps, "name"> & {
control: Control<T, object>;
disableErrorSpace?: boolean;
name: Path<T>;
maxLength?: number;
const InputField = <T extends FieldValues>({
control,
disableErrorSpace,
name,
required,
label,
maxLength = 50,
...restProps
}: Props<T>) => (
<Controller
render={({ field: { ref, ...restField }, fieldState: { error } }) => (
<TextField
[...]