I found that when i'm using useController hook for handling my form, my inputs doesn't have 'type' property. I think that is bad for semantic rules. I would like extend existing interface for properties like: type, label. Unfortunately i stuck, because of typescript.
Here is what i've tried so far.
import { useController, UseControllerProps } from 'react-hook-form';
export type FormValues = {
title: string;
amount: number;
url: string;
price: number;
};
const CustomInputHooked = (props: UseControllerProps<FormValues>) => {
const { field, fieldState } = useController(props);
console.log('props', props);
console.log('field', field);
console.log('fieldState', fieldState);
return (
<div>
<input {...field} />
<p>{fieldState.isTouched && 'Touched'}</p>
<p>{fieldState.isDirty && 'Dirty'}</p>
<p>{fieldState.invalid ? 'invalid' : 'valid'}</p>
</div>
);
};
import CustomInputHooked, { FormValues } from './custom-input/custom-input-hooked';
interface initialStateModel {
title: string;
amount: number;
url: string;
price: number;
}
const initialState: initialStateModel = {
title: '',
amount: 1,
url: '',
price: 0,
};
const AddItemHooked = () => {
const { control, handleSubmit } = useForm<FormValues>({
defaultValues: initialState,
mode: 'onChange',
});
const onSubmit = (data: FormValues) => {
console.log('SUBMIT', data);
};
return (
<div className="d-flex vh-100 justify-content-center align-items-center">
<form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
<CustomInputHooked control={control} name="title" rules={{ required: true }} />
<CustomInputHooked
control={control}
name="amount"
rules={{ required: true, maxLength: 5, pattern: /[0-9]/g }}
/>
<CustomInputHooked control={control} name="url" rules={{ required: true }} />
<CustomInputHooked control={control} name="price" rules={{ required: true }} />
<input type="submit" />
</form>
</div>
);
};
@UPDATE
I managed to do that. It is working but i'm pretty sure that my solution isn't elegant. Could you give me feedback?
I copied a lot of ts code and have added 2 more values..
import {
Control,
FieldPath,
FieldPathValue,
FieldValues,
RegisterOptions,
UnpackNestedValue,
useController,
} from 'react-hook-form';
export type FormValues = {
title: string;
amount: number;
url: string;
price: number;
};
declare type FromProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName;
rules?: Omit<
RegisterOptions<TFieldValues, TName>,
'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'
>;
shouldUnregister?: boolean;
defaultValue?: UnpackNestedValue<FieldPathValue<TFieldValues, TName>>;
control?: Control<TFieldValues>;
type?: 'text' | 'number' | 'date';
};
const CustomInputHooked = (props: FromProps<FormValues>) => {
const { field, fieldState } = useController(props);
console.log('props', props);
console.log('field', field);
console.log('fieldState', fieldState);
return (
<div>
<input {...field} type={props.type} />
</div>
);
};
CodePudding user response:
If I understand correctly, you want your CustomInputHooked
component to accept the same props as useController
, plus an extra type
prop so that you can properly specify your child native <input>
element?
In that case, you can simply use an intersection. And you can use a rest operator to separate the type
prop, so that you do not pass it to useController
.
const CustomInputHooked = ({
type,
...props
}: {
type?: 'text' | 'number' | 'date'
} & UseControllerProps<FormValues>) => {
const { field, fieldState } = useController(props);
return (
<div>
<input {...field} type={type} />
</div>
);
};