What are the types of react-hook-form methods? - reactjs

I am using react-hook-form and I want to have certain parts of the form in an extra component. Therfore I need to pass down some methods. What are the correct typescript types of the methods: register, control, setValue, watch ?
Example Sandbox
interface INameInput {
register: any;
setValue: any;
watch: any;
control: any;
}
const NameInput: React.FC<INameInput> = (props: INameInput) => {
const { register, control, setValue, watch } = props;
return (
<>
<label>First Name</label>
<input name="firstName" ref={register} />
<label>Last Name</label>
<input name="lastName" ref={register} />
</>
);
};
export default function App() {
const { register, control, setValue, watch} = useForm<FormValues>();
return (
<form >
<NameInput
register={register}
control={control}
setValue={setValue}
watch={watch}
/>
</form>
);
}

Here is a page which contains the list of exported Types
https://react-hook-form.com/ts
I think you are after the following type
https://react-hook-form.com/ts#UseFormMethods
eg:
UseFormMethods['register']

Related

react-hook-form file field TypeScript type

I'm building a contact form with react-hook-form and Typescript. Following official examples here, I've implemented all the fields but the multiple file field.
What type should I use for a multiple-file field?
import * as React from "react";
import { useForm } from "react-hook-form";
type FormData = {
firstName: string;
lastName: string;
files: // <--- this is the type I'm looking for
};
export default function App() {
const { register, setValue, handleSubmit, formState: { errors } } = useForm<FormData>();
const onSubmit = handleSubmit(data => console.log(data));
// firstName and lastName will have correct type
return (
<form onSubmit={onSubmit}>
<label>First Name</label>
<input {...register("firstName")} />
<label>Last Name</label>
<input {...register("lastName")} />
<label>Files</label>
<input type="file" multiple {...register("files")} />
<button
type="button"
onClick={() => {
setValue("lastName", "luo"); // ✅
setValue("firstName", true); // ❌: true is not string
errors.bill; // ❌: property bill does not exist
}}
>
SetValue
</button>
</form>
);
}

react-hook-form register type conflict

I want to make a custom form input field component with react-hook-form. Here is my code.
// InputField.tsx
interface InputFieldProps {
label: string
register: UseFormRegister<FieldValues>
}
const InputField = ({ label, register }: InputFieldProps) => {
return (
<div>
<label htmlFor="input">{label}</label>
<br></br>
<input
{...(register(label), { required: true })}
id="input"
type="text"
placeholder={`Enter your ${label}...`}
/>
</div>
)
}
// MyForm.tsx
interface IFormValues {
email: string
password: string
}
const MyForm = () => {
const { register, handleSubmit } = useForm<IFormValues>()
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<InputField label="password" register={register} /> // register type error
<button type="submit">Submit</button>
</form>
)
}
But I'm getting this error
Type 'UseFormRegister<IFormValues>' is not assignable to type 'UseFormRegister<FieldValues>'.
Type 'FieldValues' is missing the following properties from type 'IFormValues': email, password
How do I type the register correctly? Or is there better approach to this?
The first error is that you are creating a register from a useForm with a explicit set type IFormValues type, and the expected, as you set in the InputField.tsx, is FieldValues
//Myform.tsx
const { register, handleSubmit } = useForm<IFormValues>()
//InputField.tsx
register: UseFormRegister<FieldValues> //should be the same type as MyForm.tsx
Normally, you don't have to explicity set the type when using useForm. It will acquire the right one by getting the fields you are registering.
And the second one, you are telling your register that every field you are going to register has an email and password. So when you type:
<InputField label="password" register={register} />
Your register was kind of expecting something like:
<InputField label="password" label="email" register={register} />
My opinion: let typescript infer the correct type where it can, and use defaultValues and useFormContext. defaultValues will inter for you the type of register, so you could just copy that type into your InputField.tsx, if you want to keep passing register as a prop. useFormContext will create a register for you from the already opened useForm. I would use the following (I think its a cleaner, simpler approach):
// InputField.tsx
interface InputFieldProps {
label: string
}
const InputField = ({ label }: InputFieldProps) => {
const { register } = useFormContext();
return (
<div>
<label htmlFor="input">{label}</label>
<br></br>
<input
{...(register(label), { required: true })}
id="input"
type="text"
placeholder={`Enter your ${label}...`}
/>
</div>
)
}
// MyForm.tsx
const MyForm = () => {
const { handleSubmit } = useForm({
defaultValues: {email: '', password: ''}
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<InputField label="email"/> //i think this was missing
<InputField label="password"/>
<button type="submit">Submit</button>
</form>
)
}
OBS: if you open a form using useForm, and then another somewhere else in the same page, useFormContext will get lost and throw an error, because it does not know to which form you are referring and it does not allow you to specify it. Be careful with this.

React-Hook-Form Input Element is Empty in Inspect Element but Not in Browser

I am using react-form-hook in a project. I have css styles based on input field value attributes. react-form-hook is setting the values of all the inputs but doesn't show in inspect element niegther my css styles work. I just want to know how react-form-hook set the values on input elements when there is no actual value. Here is the code
import React, { useState } from 'react';
const CustomInput = (props: any) => {
const { label, name, className, type, onChange, register, setValue, value, disabled } = props;
return (
<div className="custom--input--container">
<input
type={type}
name={name}
ref={register}
className={`custom--input ${className}`}
onChange={e => {
onChange();
setValue(name, e.target.value, { shouldValidate: true });
}}
/>
<span className="custom--input--label">{label}</span>
</div>
);
};
export default CustomInput;
Component used here
<CustomInput
label="First Name*"
type="text"
name={SettingsFormFields.FIRST_NAME}
className={classNames({
error: errors[SettingsFormFields.FIRST_NAME],
})}
setValue={setValue}
register={register}
onChange={enableButton}
/>
value is being displaed
No value in inspect element

Only one form submitting correctly with react hooks

I have a main App component that has a form and another component that also renders a separate form. I want the second form to submit correctly using its separate handleSecondSubmit function, but it does not seem to use it.
Here's the full code and a StackBlitz: Stackblitz Demo
import React from "react";
import { useForm } from "react-hook-form";
const SecondForm = () => {
const { handleSubmit, register } = useForm();
const handleSecondSubmit = e => {
e.preventDefault();
console.log("2nd form");
};
return (
<form onSubmit={e => handleSubmit(handleSecondSubmit)}>
<input name="test" type="text" ref={register({ required: true })} />
<input type="submit" />
</form>
);
};
const App = () => {
const { register, handleSubmit, watch, errors } = useForm();
const onSubmit = data => console.log(data);
console.log(watch("example"));
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<input name="example" defaultValue="test" ref={register} />
<input name="exampleRequired" ref={register({ required: true })} />
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
<SecondForm />
</>
);
};
export default App;
Can someone explain what's happening here? Thank you
You need to call the handleSubmit directly when applying the handler, and the signature for the returned function has the event as the second argument (the first being the data)
const SecondForm = () => {
const { handleSubmit, register } = useForm();
const handleSecondSubmit = (data, e) => {
e.preventDefault();
console.log("2nd form", data);
};
return (
<form onSubmit={handleSubmit(handleSecondSubmit)}>
<input name="test" type="text" ref={register({ required: true })} />
<input type="submit" />
</form>
);
};
https://stackblitz.com/edit/react-v7cdfh
try this :
return (
<form onSubmit={e => handleSubmit(handleSecondSubmit(e))}>
<input name="test" type="text" ref={register({ required: true })} />
<input type="submit" />
</form>
);
};
you forgot to pass e to handleSecondSubmit(e)
Seems you need to pass the onSubmit event object through to your second form's handler.
onSubmit={e => handleSubmit(handleSecondSubmit(e))}
https://stackblitz.com/edit/react-fduvdv

React phone input 2 with react hook form

I am using PhoneInput along with react hook form, I want to enable save button only if phone number is valid
Code:
<form onSubmit={handleSubmit(onSubmitRequest)}>
.....................other code..............
<Controller
as={
<PhoneInput
id="pNum"
placeholder="Enter phone number"
className={classes.phoneInput}
inputRef={register({required: true})}
isValid={(inputNumber, onlyCountries) => {
return onlyCountries.some((country) => {
return startsWith(inputNumber, country.dialCode) || startsWith(country.dialCode, inputNumber);
});
}}
/>
}
name="phoneNumber"
control={control}
/>
........................other code...................
<Button
fullWidth
type="submit"
variant="contained"
color={'primary'}
className={classes.submitBtn}
data-testid="customerFormButton"
disabled={!formState.isValid}
>
Save
</Button>
</form>
Here I used PhoneInput as controller along with isValid for it. How can I disable Save button for invalid phone number input?
How are you? I believe that your problem is because you are not configuring the rules for the controller.
You need to change your controller to something like this:
<Controller
as={
<PhoneInput
id="pNum"
placeholder="Enter phone number"
className={classes.phoneInput}
inputRef={register}
isValid={(inputNumber, onlyCountries) => {
return onlyCountries.some((country) => {
return startsWith(inputNumber, country.dialCode) || startsWith(country.dialCode, inputNumber);
});
}}
/>
}
name="phoneNumber"
control={control}
rules= {{required: true}}
/>
ref cannot be currently used on this element. react-phone-input-2.
Until its supported, you can provide a hidden input field which updates its value when the phone updates its value and put the ref on that
Example:
import React, { FC, useCallback } from 'react';
import { useFormContext } from 'react-hook-form';
import PhoneInput from 'react-phone-input-2';
import 'react-phone-input-2/lib/style.css';
interface Props {
handleChange: (name: string, val: string) => void;
defaultValue: string;
name: string;
}
const MyComponent: FC<Props> = ({ defaultValue, name, handleChange }) => {
const { register, setValue, watch } = useFormContext(); // Note: needs <FormProvider> in parent for this to be acessible
const nameHidden = `${name}Hidden`;
const handleChangePhone = useCallback(
(val: string) => {
setValue(nameHidden, val, { shouldValidate: true });
handleChange(name, val);
},
[handleChange]
);
return (
<>
<PhoneInput value={defaultValue as string} country="gb" onChange={handleChangePhone} />
<input
type="hidden"
name={nameHidden}
defaultValue={defaultValue}
ref={register({
// validate stuff here...
})}
/>
</>
);
};
export default MyComponent;

Resources