react-hook-form file field TypeScript type - reactjs

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>
);
}

Related

Formik initialValues type check error ts(2322)

I have two variants of the form. The second variant (I named it "pro") have the additional field "company".
I'm trying to use one component with component instead of two components and got types error at assigning a value to the Formik initialValue prop.
<Formik
initialValues={isPro ? initValues : initProValues}
onSubmit={isPro ? signup : signupPro}
>
The error message is:
Property 'company' is missing in type 'ISignup' but required in type 'ISignupPro'.ts(2322)
App.tsx(9, 3): 'company' is declared here.
types.d.ts(147, 5): The expected type comes from property 'initialValues' which is declared here on type 'IntrinsicAttributes & FormikConfig<ISignupPro> & { children: () => Element; initialValues: ISignup; onSubmit: (values: ISignupPro) => void; }'
Here the link to sandbox with my project.
Full code of App.tsx below:
import { useState } from "react";
import { Formik, Form, Field } from "formik";
interface ISignup {
email: string;
password: string;
}
interface ISignupPro extends ISignup {
company: string;
}
const signup = (values: ISignup) => {
alert(JSON.stringify(values, null, 2));
};
const signupPro = (values: ISignupPro) => {
alert(JSON.stringify(values, null, 2));
};
export default function App() {
const [isPro, setPro] = useState(false);
const initValues: ISignup = {
email: "",
password: ""
};
const initProValues: ISignupPro = {
email: "",
password: "",
company: ""
};
return (
<div className="App">
<h1>signup form example</h1>
<div>
<label htmlFor="pro">Pro</label>
<input
type="checkbox"
id="pro"
checked={isPro}
onChange={() => setPro((prev) => !prev)}
/>
</div>
<Formik
initialValues={isPro ? initValues : initProValues}
onSubmit={isPro ? signup : signupPro}
>
{() => {
return (
<Form>
<div>
<label htmlFor="email">Email</label>{" "}
<Field id="email" name="email" type="email" />
</div>
<div>
<label htmlFor="password">Password</label>{" "}
<Field id="password" name="password" type="password" />
</div>
{isPro && (
<div>
<label htmlFor="company">Company</label>{" "}
<Field id="company" name="company" type="text" />
</div>
)}
<button type="submit">submit</button>
</Form>
);
}}
</Formik>
</div>
);
}
I'm stuck. Please help!

How to clear form when clicking off/outside of the form (formik)

I am wanting when a user clicks outside of the form all input/ selected fields clear including any stored data. I am wondering how to implement this with formik?
I know there is a resetForm() that formik has which would be ideal but am not sure how I would implement it when clicking outside the form. I have just a dummy code from formiks website.
import {
Formik,
FormikHelpers,
FormikProps,
Form,
Field,
FieldProps,
} from 'formik';
interface MyFormValues {
firstName: string;
}
export const MyApp: React.FC<{}> = () => {
const initialValues: MyFormValues = { firstName: '' };
return (
<div>
<h1>My Example</h1>
<Formik
initialValues={initialValues}
onSubmit={(values, actions) => {
console.log({ values, actions });
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}}
>
<Form>
<label htmlFor="firstName">First Name</label>
<Field id="firstName" name="firstName" placeholder="First Name" />
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
);
};

Yup Validation causing errors

I'm trying to validate my forms on React using yup and useForm, I've followed every step of the tutorial but I am still receiving errors. I am receiving 'TypeError: Cannot read property 'firstName' of undefined'. This is odd because I have already named it in the input value. How can I fix this?
P.s I have tried using {...register} and ref={register} don't seem to matter.
import React from 'react'
import { useForm } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import * as yup from "yup";
const schema = yup.object().shape({
firstName: yup.string().required("First Name should be required please"),
lastName: yup.string().required(),
email: yup.string().email().required(),
});
const Submission = () => {
const { register, handleSubmit, errors } = useForm({
resolver: yupResolver(schema),
});
const submitForm = (data) => {
console.log(data);
};
return (
<div className="Form">
<div className="title">Sign Up</div>
<div className="inputs">
<form onSubmit={handleSubmit(submitForm)}>
<input
type="text"
name="firstName"
{...register('firstName')}
placeholder="First Name..."
/>
<p> {errors.firstName?.message} </p>
<input
type="text"
name="lastName"
placeholder="Last Name..."
ref={register}
/>
<p> {errors.lastName?.message} </p>
<input
type="text"
name="email"
placeholder="Email..."
ref={register}
/>
<p> {errors.email?.message} </p>
<input type="submit" id="submit" />
</form>
</div>
</div>
);
}
export default Submission;
The exact error I am recieving looks like this:
Turns out I'm still using the outdated version of the library.
From this,
<input type="text"name="email"placeholder="Email..."ref={register}/>
const { register, handleSubmit, errors } = useForm({
resolver: yupResolver(schema),
});
To this
{...register("email", { required: "Email is Required"})};
const { register,handleSubmit, formState: { errors },} = useForm();

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

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']

React Typescript Custom Hooks: Property 'prop_name' does not exist on type '{}'

I am trying to learn React and React Hooks. I have created custom hook that lives in another file: CustomHook.ts. I am using it in my ContactForm.tsx. The problem I am having is inside each value={inputs.property} in the <input /> tags. Typescript is unable to resolve the types of each inputs._propertyName.
I have defined an interface IContact that defines the types I would like to use. I am NOT currently using this interface as I don't know where to put it.
Any help would be greatly appreciated!
Error:
ERROR in /jefthimi/src/components/Contact/ContactForm.tsx(35,31)
TS2339: Property 'subject' does not exist on type '{}'.
ERROR in /jefthimi/src/components/Contact/ContactForm.tsx(46,31)
TS2339: Property 'email' does not exist on type '{}'.
ERROR in /jefthimi/src/components/Contact/ContactForm.tsx(57,31)
TS2339: Property 'name' does not exist on type '{}'.
ERROR in /jefthimi/src/components/Contact/ContactForm.tsx(68,31)
TS2339: Property 'comments' does not exist on type '{}'.
ContactForm.tsx
import React from 'react';
import './ContactForm.scss';
import useContactForm from './CustomHook';
interface IContact {
subject: string;
email: string;
name: string;
comments: string;
}
const message = (inputs: any) => {
alert(`Message Sent!
Subject: ${inputs.subject}
Sender: ${inputs.email}
Name: ${inputs.name}
Comments: ${inputs.comments}`);
};
const { inputs, handleInputChange, handleSubmit } = useContactForm(message);
export default class ContactForm extends React.Component {
render() {
return (
<div className="contactForm_container">
<div className="contactForm_inner">
<form onSubmit={handleSubmit}>
<div className="input-group">
<label htmlFor="subject">Subject</label>
<input
id="subject"
name="subject"
type="text"
onChange={handleInputChange}
value={inputs.subject}
required
/>
</div>
<div className="input-group">
<label htmlFor="email">Your Email</label>
<input
id="email"
name="email"
type="text"
onChange={handleInputChange}
value={inputs.email}
required
/>
</div>
<div className="input-group">
<label htmlFor="name">Your Name</label>
<input
id="name"
name="name"
type="text"
onChange={handleInputChange}
value={inputs.name}
required
/>
</div>
<div className="input-group">
<label htmlFor="comments">Comments</label>
<textarea
name="comments"
id="comments"
rows={10}
onChange={handleInputChange}
value={inputs.comments}
required
/>
</div>
<div className="controls">
<button type="submit">Send Message</button>
</div>
</form>
</div>
</div>
);
}
}
CustomHook.ts
import React, { useState } from 'react';
/*
This is a Custom React Hook that handles our form submission
*/
const useContactForm = (callback) => {
const [inputs, setInputs] = useState({});
const handleSubmit = (event) => {
if (event) {
event.preventDefault();
}
callback();
};
const handleInputChange = (event) => {
event.persist();
setInputs((inputs) => ({
...inputs,
[event.target.name]: event.target.value
}));
};
return {
handleSubmit,
handleInputChange,
inputs
};
};
export default useContactForm;
The issue is that the initial state for inputs in CustomHook.ts is {}. Then you are trying to render inputs.subject, inputs.email, inputs.name and inputs.comments. These properties do not exist on the empty object {}, which is what the error messages are telling you.
Let's start with some basics. You have IContact, but you don't know what to do with it. You should be using it to type the data anywhere you expect that signature. To start with, the message callback.
const message = (inputs: IContact) => {
alert(`Message Sent!
Subject: ${inputs.subject}
Sender: ${inputs.email}
Name: ${inputs.name}
Comments: ${inputs.comments}`);
};
How about the useContactForm hook? Well, you could, but I don't think I would recommend it. When I look at that hook, I don't see anything inside of it that references IContact. In this case, the hook is more generic.
If only there was some way of adding typing that was more generic...
Well, there is. What we want to do is be able to pass in a type to use as the typing for other objects.
const useContactForm = <T>(callback: (state: T) => void) => {
//...code
}
Here, I added <T> to the front of the arrow function parameters. I also typed the callback as (value: T) => void to indicate the callback should accept a type of T as a parameter and not return anything.
Now, we need to type the useState function.
const [inputs, setInputs] = useState<T>({});
Uh-oh. {} doesn't match T. We need an initial state of type T. Since T is being passed in, our initial state will need to be as well.
const useContactForm = <T>(callback: (state: T) => void, initialState: T) => {
const [inputs, setInputs] = useState<T>(initialState);
// ...code
}
And pass it in.
const {inputs, handleInputChange, handleSubmit} = useContactForm(message, {
subject: '',
email: '',
name: '',
comments: '',
});
Okay. And that's basically how that needs to get handled. However, there are several other issues with the code and use of both TypeScript and Hooks.
You need more typings. The event parameters in the event handlers should have types. e.g. ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
You are passing a function into setInputs instead of new state.
You call the callback in handleSubmit with no arguments, but the message callback is clearly looking for an IContact.
You are trying to use hooks in a class component instead of a functional one. You need to change the class component to a function and put the call to your custom hook inside of the function.
Here is some working code.
ContactForm.tsx
import React from 'react';
import './ContactForm.scss';
import useContactForm from './CustomHook';
interface IContact {
subject: string;
email: string;
name: string;
comments: string;
}
const message = (inputs: IContact) => {
alert(`Message Sent!
Subject: ${inputs.subject}
Sender: ${inputs.email}
Name: ${inputs.name}
Comments: ${inputs.comments}`);
};
export default () => {
const {inputs, handleInputChange, handleSubmit} = useContactForm(message, {
subject: '',
email: '',
name: '',
comments: '',
});
return (
<div className="contactForm_container">
<div className="contactForm_inner">
<form onSubmit={handleSubmit}>
<div className="input-group">
<label htmlFor="subject">Subject</label>
<input
id="subject"
name="subject"
type="text"
onChange={handleInputChange}
value={inputs.subject}
required
/>
</div>
<div className="input-group">
<label htmlFor="email">Your Email</label>
<input
id="email"
name="email"
type="text"
onChange={handleInputChange}
value={inputs.email}
required
/>
</div>
<div className="input-group">
<label htmlFor="name">Your Name</label>
<input
id="name"
name="name"
type="text"
onChange={handleInputChange}
value={inputs.name}
required
/>
</div>
<div className="input-group">
<label htmlFor="comments">Comments</label>
<textarea
name="comments"
id="comments"
rows={10}
onChange={handleInputChange}
value={inputs.comments}
required
/>
</div>
<div className="controls">
<button type="submit">Send Message</button>
</div>
</form>
</div>
</div>
);
};
CustomHook.ts
import React, {useState, FormEvent, ChangeEvent} from 'react';
/*
This is a Custom React Hook that handles our form submission
*/
const useContactForm = <T>(callback: (state: T) => void, initialState: T) => {
const [inputs, setInputs] = useState<T>(initialState);
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
if (event) {
event.preventDefault();
}
callback(inputs);
};
const handleInputChange = (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
event.persist();
setInputs({
...inputs,
[event.target.name]: event.target.value,
});
};
return {
handleSubmit,
handleInputChange,
inputs,
};
};
export default useContactForm;
The proposed solution is to define an interface that contains our Message properties, then create a default Message object that has initialized fields.
interface IMessage {
subject: string;
email: string;
name: string;
comments: string;
}
const message: IMessage = {
subject: '',
email: '',
name: '',
comments: ''
};
We then setState with this initialized object.
const [inputs, setInputs] = useState(message);

Resources