Building forms with React and Typescript + Antd

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"
}