I'm working on this code to submit a form using React with Typescript and Material-UI:
export default function OpenTicket(props: any) {
const classes = useStyles();
const formHandler = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(e.target);
};
return (
<Container>
<Box m={5}>
<div className={classes.root}>
<form>
<TextField
id="outlined-full-width"
label="Name"
/>
<Button
variant="contained">
Submit
</Button>
</form>
</div>
</Box>
</Container>
);
}
Request:
export interface TicketDTO {
title?: string;
}
export async function postTicket(): Promise<AxiosResponse<TicketDTO[]>> {
return await axios.post<TicketDTO[]>(
`${baseUrl}/support/tickets/create`
);
}
What is the proper way to implement a field validation and make a call when Submit button is pressed?
To answer your comment, this is how you usually would use react-hook-form:
import { useForm } from "react-hook-form";
export default function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
// This won't be run unless all the input validations are met.
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true, maxLength: 20 })} />
{errors.firstName && <span>This field is required</span>}
<input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} />
{errors.lastName && <span>This field can't be a number</span>}
<input type="number" {...register("age", { min: 18, max: 99 })} />
{errors.age && <span>This needs to be between 18 and 99 range</span>}
<input type="submit" />
</form>
);
}
The register function is the one that handles the validation, you can read all the available validations here.
https://react-hook-form.com/api/useform/register
I think this eliminates a lot of code duplication to handle the inputs using react state, plus it also makes the validation and integration with external UI libraries very easy.
I have this sandbox in case you want to see the code in action.
Edit:
To perform a post request like the one in your question, and using the typing that you used, you can do something like this:
import axios, { AxiosResponse } from "axios";
import { useForm } from "react-hook-form";
import "./styles.css";
interface TicketDTO {
title: string;
}
async function postTicket(
data: TicketDTO
): Promise<AxiosResponse<TicketDTO[]>> {
return await axios.post<TicketDTO[]>(`baseUrl/support/tickets/create`);
}
export default function App() {
const {
register,
handleSubmit,
formState: { errors }
// This generic will type the data response correctly.
// So the errors object and register function will infer the types of TicketDTO.
} = useForm<TicketDTO>();
// This won't be run unless all the input validations are met.
const onSubmit = async (data: TicketDTO) => {
// This console.log won't give errors
// console.log(data.title);
// This console.log will give typing errors
// console.log(data.randomValue);
try {
await postTicket(data);
} catch (err) {
console.log(err);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("title", { required: true, maxLength: 20 })} />
{errors.title && <span>This field is required</span>}
<input type="submit" />
</form>
);
}
Here's the sandbox and the post is not working because there's no valid base url, but it should do what you want to.
Edit without react hook form.
This example uses react state and as I mention is more work to do since you will have to do the validations by yourself.
import axios, { AxiosResponse } from "axios";
import React from "react";
import "./styles.css";
interface TicketDTO {
title: string;
}
async function postTicket(
data: TicketDTO
): Promise<AxiosResponse<TicketDTO[]>> {
return await axios.post<TicketDTO[]>(`baseUrl/support/tickets/create`);
}
export default function App() {
// You will have to define your custom fields in separate states or one object state. this uses a separate state per input.
const [title, setTitle] = React.useState("");
// You will have to define the errors as well.
const [errors, setErrors] = React.useState<TicketDTO>({
title: ""
});
const onChange = (event: React.ChangeEvent<HTMLInputElement>) =>
setTitle(event.target.value);
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
// Prevents browser to load the document
event.preventDefault();
// Here you will have to do logic to handle all your inputs.
if (title) {
try {
await postTicket({ title });
} catch (err) {
console.error(err);
}
setErrors({ title: "" });
} else {
setErrors({ title: "Title is required" });
}
};
return (
<form onSubmit={onSubmit}>
<input name="title" value={title} onChange={onChange} />
{errors.title && <span>This field is required</span>}
<input type="submit" />
</form>
);
}
Also you can take advantage of the HTML5 native validations if you don't want to do the validations in react. so it is just a matter of doing this:
import axios, { AxiosResponse } from "axios";
import React from "react";
import "./styles.css";
interface TicketDTO {
title: string;
}
async function postTicket(
data: TicketDTO
): Promise<AxiosResponse<TicketDTO[]>> {
return await axios.post<TicketDTO[]>(`baseUrl/support/tickets/create`);
}
export default function App() {
const [title, setTitle] = React.useState("");
const onChange = (event: React.ChangeEvent<HTMLInputElement>) =>
setTitle(event.target.value);
const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
// Prevents browser to load the document
event.preventDefault();
try {
await postTicket({ title });
} catch (err) {
console.error(err);
}
};
return (
<form onSubmit={onSubmit}>
{/* required prop makes usage of native html5 validation */}
<input name="title" value={title} required onChange={onChange} />
<input type="submit" />
</form>
);
}