Building forms with React and Typescript + Antd
When building forms it can get easily hideous taking care of all the validation and when using Typescript we need to ensure we keep our models aligned. Luckily the Antd design system comes here to the rescue, especially when quickly building some sort of Management Dashboard application. Not only does Antd help you building forms but also creating complex layouts, Client Side error handling with Notifications and much more.
For building a quick prototype we make use of create react app
that will help us setting up a React project with Typescript included. To do so, run npx create-react-app my-app --template typescript
in the new project directory.
Once it is done installing all dependencies, change to the directory and run npm start
.
We now install the Antd library, we can do this easily the same way by running npm install antd
and we are ready to build our application.
Within the src
directory we can find the main app component where we delete all the clutter code and start freshly with setting up a simple Form via Antd.
We can import import { Button, Form } from 'antd';
those components from the antd library. Then lets add our Form component <Form></Form>
. The Form component comes with numerous props, for now we just add the onFinish
prop which will be called once we submit the form. This callback will return the values we inserted into the form. The interesting feature is, we can name our form items (input, date selection etc.) in a way it can match our typescript model once submitting the form.
For our example application we will use the follow model.
interface Address {
street: string
postalCode: number
city: string
}
interface FormData {
firstname: string
lastname: string
address: Address
}
In our form callback we return now our model and we just log it for now.
const onFormSubmit = useCallback((values: FormData): void => {
console.log(values)
},[])
Now let's add the actual form elements, so we can play around with actual data. First we add the components for getting our firstname and lastname. With Antd we have to use <Form.Item>
components nested within our Form, so Antd can get the values and validate and so on.
<Form onFinish={onFormSubmit}>
<Form.Item initialValue="" label="firstname" name="firstname">
<Input name="firstname" />
</Form.Item>
<Form.Item initialValue="" label="lastname" name="lastname">
<Input name="lastname" />
</Form.Item>
</Form>
If our fields are optional, we don't want undefined values being submitted since those might not be handled by our backend, so we define initial values via the initialValue
prop. The label
prop will as the name suggests add a label to our field. The name prop value will be used as key when submitting our form. Nested within the Form.Item
we added the actual Input
component, those are self explainatory.
Now the interesting part, our address propery in our model is an actual object but fortunately antd makes it very uncomplicated to create a nested structure.
<Form.Item name={["address", "street"]} label="street">
<Input name="street" />
</Form.Item>
<Form.Item name={["address", "postalCode"]} label="postalCode">
<Input name="postalCode" />
</Form.Item>
<Form.Item name={["address", "city"]} label="city">
<Input name="city" />
</Form.Item>
Instead of passing just a string to the name prop of the Form.Item
we can actually pass an array of strings, address
will be our key and the second string our value. The only thing missing is now a submit button to get actually our form data to process it.
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
The htmlType
prop ensures this button will be used to Submit the form.
Our entire component should now look something like this:
import { Button, Form, Input } from 'antd';
import React, { useCallback } from 'react';
interface Address {
street: string
postalCode: number
city: string
}
interface FormData {
firstname: string
lastname: string
address: Address
}
function App() {
const onFormSubmit = useCallback((values: FormData): void => {
console.log(values)
},[])
return (
<Form onFinish={onFormSubmit}>
<Form.Item initialValue="" label="firstname" name="firstname">
<Input name="firstname" />
</Form.Item>
<Form.Item initialValue="" label="lastname" name="lastname">
<Input name="lastname" />
</Form.Item>
<Form.Item name={["address", "street"]} label="street">
<Input name="street" />
</Form.Item>
<Form.Item name={["address", "postalCode"]} label="postalCode">
<Input name="postalCode" />
</Form.Item>
<Form.Item name={["address", "city"]} label="city">
<Input name="city" />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>
);
}
export default App;
When now running npm start
and submitting our form, we should our data matching our model.
{
address: {
city: "Almere",
postalCode: "1234",
street: "Example Street"
},
firstname: "Philipp",
lastname: "Rost"
}