Building a React Form Component with TypeScript: Submitting
This is the last post in a series of blog posts where we are building our own super simple form component in React and TypeScript. In the last post we encapsulated validation so that the consumer of our components needs to do a minumim amount of work to include some basic form validation. In this post we’ll submit our form to our web api.
Submitting the form to our web API
Our form is coming along nicely with all that nice validation.
It’s time to submit the form to a real web api. All we need to do is implement Form.submitForm
that we created in the very first post:
/**
* Submits the form to the http api
* @returns {boolean} - Whether the form submission was successful or not
*/
private async submitForm(): Promise<boolean> {
try {
const response = await fetch(this.props.action, {
method: "post",
headers: new Headers({
"Content-Type": "application/json",
Accept: "application/json"
}),
body: JSON.stringify(this.state.values)
});
return response.ok;
} catch (ex) {
return false;
}
}
We simply call our api using the fetch function, passing the values from the form and returning whether it was successful or not.
So, if we fill in and submit a form, the web api should be called … and if the call is successful we should get confirmation at the bottom of the form:
But what if the api errors? Let’s force an error in the api:
(this is from an asp.net core backend)
[Route("api/contactus")]
public class ContactUsController : Controller
{
[HttpPost]
public async Task<IActionResult> Post([FromBody]ContactUsPost contactUsPost)
{
throw new Exception("an error");
...
}
}
This is what we get:
That’s perfect. However what if a validation error occurs on the server? I know we should try to replicate all the validation on the client to get a great UX but that doesn’t always happen …
Let’s fake a validation error in our api:
(again this is asp.net core code)
[Route("api/contactus")]
public class ContactUsController : Controller
{
[HttpPost]
public async Task<IActionResult> Post([FromBody]ContactUsPost contactUsPost)
{
ModelState.AddModelError("Notes", "These notes aren't good enough!");
return BadRequest(ModelState);
...
}
}
This is what we get:
Well that’s not perfect! Ideally we would show the specific validation error that is returned in the response. So, let’s get to work and improve our Form
component …
In submitForm()
we branch off if we receive a 400. In the new branch, we inspect the response and map any errors to the IErrors
format that Form
expects. We then update the errors
state.
private async submitForm(): Promise<boolean> {
try {
const response = await fetch(this.props.action, {
method: "post",
headers: new Headers({
"Content-Type": "application/json",
Accept: "application/json"
}),
body: JSON.stringify(this.state.values)
});
if (response.status === 400) { /* Map the validation errors to IErrors */ let responseBody: any; responseBody = await response.json(); const errors: IErrors = {}; Object.keys(responseBody).map((key: string) => { // For ASP.NET core, the field names are in title case - so convert to camel case const fieldName = key.charAt(0).toLowerCase() + key.substring(1); errors[fieldName] = responseBody[key]; }); this.setState({ errors }); } return response.ok;
} catch (ex) {
return false;
}
}
So, if we submit the form again to our api with the fake a validation error, we get:
Now this is perfect!
Wrapping up
Submitting the form to the web api is a straightforward task. It’s worth including a little extra code to deal with any validation errors that have come back from the server that escaped the frontend to give a good user experience.
So, we’ve reached the end of this series and built a nice component to reduce the amount of boilerplate in our forms. I hope you have found this interesting and useful!
If you to learn more about using TypeScript with React, you may find my course useful: