DEV Community

Cover image for Building a Phone Book Application with Typescript and React
James Oyanna
James Oyanna

Posted on • Updated on

Building a Phone Book Application with Typescript and React

A phone book is a directory used to store and organize contact information.

In this article, we will be developing a phonebook application using TypeScript and React.
TypeScript is a typed superset of JavaScript that allows us to catch errors at compile-time

Our phone book application will allow users to add, edit, and delete contacts.

By the end of this article, you will have a fully functional phone book application built with TypeScript and React.

Here is how the final application will look like

Final Application

View Demo Here

We will be using vite to setup our application. Vite is a build tool that is specifically designed for modern web development.

Note: Ensure that Node.js version 14.18+ is installed on your system

You can download Node.js from the official website here, and npm will be installed automatically with Node.js.

Firstly, navigate to the directory where you want to develop your application and scaffold your application with Vite. We can do this by running the following command:

npm create vite@latest

This would ask you for the project name, framework, and variant.

– For project name, you can enter phonebook

– For framework, select React from the list of options

– And for variant, select TypeScript.

When its done, navigate into your project folder by running the command cd phonebook then run the command
npm install to install all required dependencies and then npm run dev to start your application.

You can open your project on Visual Studio code.
Now let's install the dependencies we will need for this application.

Open the command line or bash on your project directory and run the command:
npm install bootstrap@5.2.3 react-bootstrap@2.7.0 react-icons@4.7.1

Styling the application:

To add styling to our phonebook application, open the index.css file existing in your project folder, clear-off the existing styles and add the following content:

Next, you can access the main.tsx file in your project folder and place the import statement for the bootstrap CSS before the import statement for index.css, as shown below:

import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css'
Enter fullscreen mode Exit fullscreen mode

Inside your src folder, create a new folder. Name it components. Create a new file, call it Header.tsx and add the following content to it.

Here, we import the "FC" (Functional Component) type from the 'react'.

We then define the Header component as a function component. The ": FC" part after "Header" specifies that the component is of type "FC", which is a shorthand for "React.FunctionComponent". This type is used to indicate that the component is a function that returns JSX.

The function itself returns a JSX element, which in this case is a header element with an h1 element inside it.
You can now test the application by run this command npm run dev.
If every thing works fine, you should see this page display on your browser:

Image description

Now let's create the form that would enable us to take input from users.

Create a new file in the components. Name it ContactForm.tsx and add the following code to it.

Here, we import several items from the 'react' and 'react-bootstrap' packages:

We defined the component as a functional component, using the FC type from React. The function is named ContactForm.

We are using the useState hook to initialize a piece of state called contact, which is an object that contains the values of our form fields. Initially, all of the fields are empty strings.

We then define an event handler function called handleOnChange that will be called whenever the user types something into one of the form fields.

This function uses destructuring to get the name and value properties of the target object, which is the input element that triggered the event.

It then uses the setContact function to update the contact state, by returning a new object that has the same properties as the previous contact object, but with the name property set to the new value.

We defined another event handler function called handleOnSubmit that will be called when the user submits the form.

This function prevents the default form submission behavior (which would cause a page refresh) and logs the contact object to the console.
We have not implemented the logic for submitting the form yet.

We returned a Form element from the react-bootstrap package. The onSubmit prop is set to the handleOnSubmit function, so that it will be called when the form is submitted. The form also has a className of "contact-form".

The component returns several Form.Group elements from the react-bootstrap package. Each Form.Group represents a form field, with a controlId prop that uniquely identifies it.

The Form.Control element within each Form.Group represents the actual input field, with props for name, value, type, placeholder, and onChange.

The component also returns a Button element from the react-bootstrap package, which is used to submit the form. It has a variant of "primary", a type of "submit", and a className of "submit-btn".

Now, let's add our Header and ContactForm component to our App.tsxcomponent. Open the App.tsx file and import these components there. Your App.tsx file should now look like this:

Checking your application on your broswer, it should now look like this:

Image description

Storing submitted contact information:

Now, let's display the added phone contacts on the page.
To properly manage and store our contacts, we will be using the useReducer hook, a built-in hook in React that would enable us to manage state using a reducer function.

Adding types to our application:

In your src folder, create a file, name it types.ts and add the following content to it:

Here, we have two interfaces- Contact and Action interface.

The Contact interface defines the shape of an object that represents a contact.

The Action interface defines the shape of an object that represents an action.

Create a new folder inside your src folder and name it reducers. Create a file inside the reducers folder and name it contactsReducer.ts and add the following content into it:

Here, we firstly import the Contact and Action interfaces from the types.ts file

We then define an interface called AppState which has a single property, contacts, which is an array of Contact objects. This represents the state of the application that this reducer manages.

We define and export the function contactsReducer which takes two arguments: state and action.

The state argument represents the current state of the application, while the action argument represents an action that has been dispatched to update the state.

The return value of this function is the new state of the application.

We use a switch statement to check the type property of the action argument to determine what action is being performed.

In this case, the type property is expected to be a string that matches the value "ADD_CONTACT", which will be defined later.

We returned a new object that represents the new state of the application.

It copies all the properties of the previous state using the spread operator ...state, and then updates the contacts property with a new array that includes all the previous contacts (...state.contacts) plus the new contact that was passed in the action.payload property.

default: return state;: This default case returns the current state unchanged if the action type is not recognized.

In order to use the contactsReducer, let's import it into App.tsx.
Now, open the App.tsx file and update the code inside to this one below:

const initialState: AppState = { contacts: [] };. In this line we define the initial state for thecontactsReducer.

const [state, dispatch] = useReducer(contactsReducer, initialState);

In this line of code we use the useReducer hook from React to create a state management system for the App component.

useReducer takes two arguments: the first argument is a reducer function, and the second argument is the initial state.

In this case, the contactsReducer function is used as the reducer, and the initialState object is passed as the initial state.

useReducer returns an array with two elements: the current state (state) and a dispatch function that can be used to dispatch actions to update the state.

The state variable is used to store the current state of the component, while dispatch is a function that can be used to update the state by dispatching actions to the contactsReducer function.

<ContactForm dispatch={dispatch} />
Here, We pass the dispatch function as a prop to the ContactFormcomponent so we can dispatch the action to add a contact to the contacts array.

You will get a Typescript error highlighted at dispatch. This is because when we pass any prop to the ContactForm component, we need to state the type of the dispatch prop in the component props list.

In order to specify the dispatch prop type, you can declare an interface named ContactFormProps above the component declaration in the ContactForm.tsx file.

Add this code to your ContactForm.tsx file

interface ContactFormProps {
  dispatch: React.Dispatch<Action>;
}
Enter fullscreen mode Exit fullscreen mode

We use it inside the angle brackets after : FC.

Now, we use the dispatch prop like this:

const ContactForm: FC<ContactFormProps> = (props) => {
const { dispatch } = props;
// ....
};

Also ensure you add an import for the Action type at the top of the file like this:

import { Action } from '../types';

Your updated code in the ContactForm.tsx file should now look like this:

So what we do in the code is to indicate that the ContactFormcomponent will receive a prop with the name dispatch whose type is React.Dispatch.

Implementing the logic to submit our form:

inside the handleOnSubmit method in ContactForm.tsx file, add the following code:


 dispatch({
      type: 'ADD_CONTACT',
      payload: contact
    })
Enter fullscreen mode Exit fullscreen mode

Here, we dispatch an action to the Redux store using the dispatch() function.
The action is an object with a type property set to the string 'ADD_CONTACT' and a payload property set to the variable contact.

Your ContactForm.tsx file will look like this:

Adding the contact List Page:

Now, we want to display all the contacts we will be adding using the form.
Let's create a Page to handle that.
Inside your components folder, create a file, call it ContactList.tsx and add the following content.

Here, the ContactList component takes in an object with a contacts property of type Contact as its props. The component then renders a table that displays the contacts in the contacts prop.

The component returns a JSX that renders the contacts array as a table. It maps over the array and renders a table row for each Contact object, displaying their firstName, lastName, phoneNumber and address properties in separate columns.

We can now display the ContactList component in the App.tsx file.

Import the ContactList.tsx file into the App.tsx file and add it below the ContactForm component.

Your App.tsx file should now look like this:

Here, we added a conditional statement that renders the ContactList component if the contacts array in the state has any items in it. If the array is empty, the ContactListcomponent will not be rendered.

If you check your browser console, you will see a warning in the console as shown below:

Image description

This warning message is indicating that each child element of the list returned by the map function in ContactListshould have a unique "key" prop.

In React, when rendering a list of elements, it's important to provide a unique key for each item in the list.

This allows React to efficiently update and re-render only the elements that have changed, instead of re-rendering the entire list every time the state changes.

Let's fix this.

In the ContactForm.tsx file, change the below code:

dispatch({
  type: 'ADD_CONTACT',
  payload: contact
});
Enter fullscreen mode Exit fullscreen mode
to this one:
dispatch({
  type: 'ADD_CONTACT',
  payload: {
    id: Date.now(), // returns current timestamp
    ...contact
  }
});
Enter fullscreen mode Exit fullscreen mode

Here, we have added extra id property to the payload

Upon adding it, Typescript error will be underlined on it. Let's fix it.

Open the types.ts file and add id to the Contact interface like this:


export interface Contact {
  id: number;
    firstName: string;
    lastName: string;
    phoneNumber: string;
    address: string;
    email: string;
  }
Enter fullscreen mode Exit fullscreen mode

Now open the ContactList.tsx file and add the id as key prop to the child trelements that are returned by the map function like this:

{contacts.map(({ id, firstName, lastName, phoneNumber, address, email }) => (
  <tr key={id}>
    <td>{firstName}</td>
    <td>{lastName}</td>
    <td>{phoneNumber}</td>
    <td>{address}</td>
    <td>{email}</td>
  </tr>
))}
Enter fullscreen mode Exit fullscreen mode

Let's do a bit of refactoring to our code.

We will separate the component for displaying different information so it’s easy to re-use and test that component when required.

Create a ContactItem.tsx file inside the components folder and move the contents from the map method of the ContactList.tsx file inside it.

Your ContactItem.tsx file will look like this:

Then change the map method of the ContactList.tsx file from the below code:

{
  contacts.map(({ id, firstName, lastName, phoneNumber, email, address }) => (
    <tr key={id}>
      <td>{firstName}</td>
      <td>{lastName}</td>
      <td>{phoneNumber}</td>
      <td>{email}</td>
      <td>{address}</td>
    </tr>
  ));
}
Enter fullscreen mode Exit fullscreen mode

To this:

 {contacts.map(({ firstName, lastName, phoneNumber, address, email, id}) => (
              <ContactItem
              key={id}
              id={id}
              firstName={firstName}
              lastName={lastName}
              phoneNumber={phoneNumber}
              email={email}
              address={address}
              />
            ))}
Enter fullscreen mode Exit fullscreen mode

Do ensure you import the ContactItem into the ContactList.tsx like this:
import ContactItem from './ContactItem';

Now let's add the ability to Edit and Delete a phone contact.

We will add the Edit and Delete icons using the react-icons library.

We have installed the library when we were starting the project.

In the ContactList.tsx file, let's add two additional items for editing and deleting in the table heading like this.

 <thead className='contacts-list-header'>
            <tr>
              <th>First Name</th>
              <th>Last Name</th>
              <th>Phone</th>
              <th>Email Address</th>
              <th>Adress</th>
              <th>Edit</th>
              <th>Delete</th>
            </tr>
          </thead>
Enter fullscreen mode Exit fullscreen mode

Now, open the ContactItem.tsx file and replace it with this contents:

Here, we added this - import { AiFillDelete, AiFillEdit } from 'react-icons/ai'; to import the Edit and Delete icons from the react-icon library.


     <td>
        <AiFillEdit size={20} color='blue' className='icon' />
      </td>
      <td>
        <AiFillDelete size={20} color='red' className='icon' />
      </td>
Enter fullscreen mode Exit fullscreen mode

We added this code to make use of our icons in the table data.

Your application should now look like this on your browser:

Phone-contact app

We will now implement the functionality for editing a contact.

In order to show the edit modal popup, we will make use of the react-bootstrap modal component.

Inside your component folder, create a file and name it EditModal.tsx.
Add the this content below inside the file.

Here, we define a EditModal functional component. The component accepts toggleModal, dataToEdit, showModal, dispatch props.

– toggleModalwhich is a function that will toggle the state value used to hide or show the modal

– dataToEditwill be an object containing contact details of the contact to be edited

– showModalwill be a boolean value indicating whether to show or hide the modal

– dispatchfunction which will be helpful to dispatch the edit contact action

The component uses the Modal component from the react-bootstrap package to render a modal dialog that displays a form for editing contact information. The ContactFormcomponent is used to render the form.

We also define the EditModalPropsinterface, which describes the shape of the props object that the component receives.

Right now, our ContactFormcomponent accepts only the dispatch prop so let’s add the remaining required prop in the definition of the type.

Open the ContactForm.tsx file and change the interface to the below code:

interface ContactFormProps {
  dispatch: React.Dispatch<Action>;
  dataToEdit: Contact | undefined;
  toggleModal: () => void;
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to add an import for the Contact and Action at the top of the file like this:

import { Action, Contact } from '../types';

Now, update state object in the ContactFormcomponent to the content below:

const ContactForm: FC<ContactFormProps> = ({  dispatch, dataToEdit, toggleModal}) => {
  const [contact, setContact] = useState({
    firstName: dataToEdit?.firstName ? dataToEdit.firstName : '',
    lastName:  dataToEdit?.lastName ? dataToEdit.lastName : '',
    phoneNumber: dataToEdit?.phoneNumber ? dataToEdit.phoneNumber : '',
    address: dataToEdit?.address ? dataToEdit.address : '',
    email:   dataToEdit?.email ? dataToEdit.email : '',
  });
Enter fullscreen mode Exit fullscreen mode

Here we created a state object for the contact form, and populating its initial values based on any data that might be passed to the component for editing.

If any of the fields are edited by the user, the 'setContact' function can be used to update the state and re-render the component with the new values.

Update the handleOnSubmitmethod to the below code:

const handleOnSubmit: FormEventHandler<HTMLFormElement> = (e) => {
    e.preventDefault();
    if(!dataToEdit){
      dispatch({
        type: 'ADD_CONTACT',
        payload:{
          id: Date.now(),
          ...contact,
        }

      })
    } else {
      toggleModal();
    }
  };
Enter fullscreen mode Exit fullscreen mode

So at application startup, the 'dataToEdit' variable will be undefined if the user has not clicked on the edit icon. If the edit icon has been clicked, 'dataToEdit' will contain the contact details to be edited.

To handle this, we check if we are currently editing a contact. If we are not, we dispatch the 'ADD_CONTACT' action to add the new contact. If we are editing a contact, we simply close the modal without taking any further action at the moment.

Once we have dispatched the 'ADD_CONTACT' action, we are calling the toggleModal function to close the modal.

Modify the code for the 'Add Contact' button as follows:

<Button variant='primary' type='submit' className='submit-btn'>
  Add Contact
</Button>
Enter fullscreen mode Exit fullscreen mode

to this code:

<Button variant='primary' type='submit' className='submit-btn'>
  {dataToEdit ? 'Update Contact' : 'Add Contact'}
</Button>
Enter fullscreen mode Exit fullscreen mode

By default, the contact form will display the 'Add Contact' button text. If the user clicks on the edit icon, the 'dataToEdit' prop value will be set, and the modal will display the 'Update Contact' text while the user edits the contact information.

Now replace the current content of the App.tsx file with the following code:

Here, two additional states have been declared in the code above.

The code includes two useStatefunctions. The first one controls whether the modal is visible or hidden, while the second one stores the details of the edited contact as an object.

Initially, the object is undefined, but once a value is set, it is of the Contact type with properties such as id, firstName, lastName, and phoneNumber.

To avoid any future TypeScript errors while using dataToEditor setDataToEdit, the type is declared in angle brackets.

In the useEffect hook, the dataToEdit value is set back to undefined once the modal is closed, i.e., when the showModalvalue is false.

In the toggleModalfunction, the updater syntax of state is used to change the showModal state from true to false and vice versa.

If the modal is already open because the showModalvalue is true, calling the toggleModalfunction will close the modal. On the other hand, if the modal is already closed, calling the toggleModalfunction will display the modal.

As you would observed in the App.tsx file, dispatch and handleEdit are passed as props to the ContactList component. Therefore, we need to accept them in the ContactList component.

To do this, open the ContactList.tsx file and replace its contents with the following code:

We can observe here that, handleEdit function takes id as a parameter and does not return anything. Therefore, it has been defined in the ContactListProps interface as follows:

interface ContactListProps {
  // ...
  handleEdit: (id: number) => void;
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Next, we extract the handleEdit and dispatch as props and pass them to the ContactItem component as illustrated below:

 <ContactItem
  key={contact.id}
  {...contact}
  handleEdit={handleEdit}
  dispatch={dispatch}
  />
Enter fullscreen mode Exit fullscreen mode

Now, open the ContactItem.tsx file and replace it with the following content:

Earlier, the ContactItem component was solely receiving attributes from the Contact interface.

we are now also providing the handleEdit and dispatch props, and thus, we have defined another interface as follows:

interface ExtraProps {
  handleEdit: (id: number) => void;
  dispatch: React.Dispatch<Action>;
}
Enter fullscreen mode Exit fullscreen mode

Upon reviewing the application, it can be observed that the modal displays the contact information of the selected contact being edited, as depicted below:

Application displaying modal

Implementing Edit functionality:

We currently have the user interface to edit and delete. Our next step is to include the code that will enable these functionalities.

To achieve this, you should access the contactsReducer.ts file and append a switch case for the UPDATE_CONTACT action.
Your contactsReducer.ts file should now look like this:

Here, The id of the contact and the updates object containing the new data are being sent for updating, as evident from the above.

The contact with the matching id is being updated using the array map method.
However, upon saving the file, a TypeScript error occurs with this case.

Upadate error

The reason for the error is that the Action interface specifies the payload as a Contact object with properties including id, firstName, lastName,, address and phoneNumber.

However, when handling the UPDATE_CONTACT case, we are only interested in the id and updates properties.

To resolve the error, we must inform TypeScript that the payload can be either a Contact object or a type that includes id and updates properties.

Now, in your types.ts file, let's add a new interface as shown below:


export interface Update {
  id: number;
  updates: Contact;
}
Enter fullscreen mode Exit fullscreen mode

Then update the Action interface to this:

export interface Action {
type: 'ADD_CONTACT' | 'UPDATE_CONTACT'
payload: Contact | Update;
}

After this modification, payload is now a union type that can be either of Contact type or Update type, and type is a string literal type that can be either 'ADD_CONTACT' or 'UPDATE_CONTACT'.

Your types.ts file should now look like this:

Next, import the new action interface into the contactsReducer.ts file

However, you may encounter additional TypeScript errors in your contactsReducer.ts once you make these changes. Lets see how to fix them.

So change ADD_CONTACT switch case from the below code:

case 'ADD_CONTACT':
  return {
    ...state,
    contacts: [...state.contacts, action.payload]
  };
Enter fullscreen mode Exit fullscreen mode

to this code:

case 'ADD_CONTACT':
  return {
    ...state,
    contacts: [...state.contacts, action.payload as Contact]
  };
Enter fullscreen mode Exit fullscreen mode

In this code above, we are using the as keyword for type casting, which explicitly informs TypeScript about the type of action.payload.

And now change the UPDATE_CONTACT switch case from the below code:

case 'UPDATE_CONTACT':
  const { id, updates } = action.payload;
    return {
      ...state,
      contacts: state.contacts.map((contact) => {
        if (contact.id === id) {
          return {
            ...contact,
            ...updates
          };
        }
        return contact;
      })
    };
Enter fullscreen mode Exit fullscreen mode

To this one:

case 'UPDATE_CONTACT':
  const { id, updates } = action.payload as Update;
    return {
      ...state,
      contacts: state.contacts.map((contact) => {
        if (contact.id === id) {
          return {
            ...contact,
            ...updates
          };
        }
        return contact;
      })
    };
Enter fullscreen mode Exit fullscreen mode

By specifying the type of action.payload as Update for the UPDATE_CONTACT case, we have resolved the TypeScript errors.

With this modification, the updated contactsReducer.ts file will now look like this:

To update the contact, you need to dispatch the UPDATE_CONTACT action. You can do this by adding the following code inside the handleOnSubmit method of the ContactForm.tsx file, before calling the toggleModal function:

Open the ContactForm.tsx file and inside the handleOnSubmit method, in the else block, before the toggleModal function call, add the below code:

dispatch({
  type: 'UPDATE_CONTACT',
  payload: {
    id: dataToEdit.id,
    updates: {
      id: Date.now(),
      ...contact
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

And inside the if condition after the dispatch call, add the below code:

setContact({
  firstName: '',
  lastName: '',
  phoneNumber: '',
  address: '',
  email: ''
});
Enter fullscreen mode Exit fullscreen mode

In this code, we are resetting the form state. This ensures that when we dispatch the ADD_CONTACT action, all the user-entered data is cleared out.

Now, if you check the application, you will notice that the contact can be edited successfully.

Upon adding the code in your handleSubmit, your final ContactForm.tsx file should now look like this:

Implementing Delete Contact Functionality:

Let us proceed with the addition of the delete contact feature by opening the contactsReducer.ts file and including a switch case for the DELETE_CONTACT action.


case 'DELETE_CONTACT': {
    const { id } = action.payload;
    return {
      ...state,
      contacts: state.contacts.filter((contact) => contact.id !== id)
    };
  }
Enter fullscreen mode Exit fullscreen mode

To remove a contact, we include only its id in the payload property and apply the array filter method to eliminate the corresponding contact

Now, in the types.ts file, change the Action interface to the following code:

export interface Action {
  type: 'ADD_CONTACT' | 'UPDATE_CONTACT' | 'DELETE_CONTACT'
  payload: Contact | Update;
}
Enter fullscreen mode Exit fullscreen mode

Here, we have included DELETE_CONTACT as an additional type.

Open the ContactItem.tsx file and, within the Delete icon, add an onClick attribute, an event handler that would be triggered when a user clicks on the delete button.

   <AiFillDelete size={20}     onClick={() => {
            const confirmDelete = window.confirm(
              `Are you sure you want to delete contact for user ${firstName} ${lastName}?`
            );
            if (confirmDelete) {
              // dispatch action
            }
          }} color='red' className='icon' />
Enter fullscreen mode Exit fullscreen mode

Here have added a function to display a confirmation dialog box asking the user if they want to delete the contact for the user whose first name and last name are stored in the firstName and lastName variables.

If the user confirms the delete action, the dispatch function is called to send an action to the store.

Your ContactItem.tsx file should now look like this:

However, upon saving the file, a TypeScript error will appear, which is demonstrated below:

Delete Error

The reason for the error is that the Action interface in the contactsReducer.ts file specifies that the payload can be either a Contact or Update, but for the DELETE_CONTACT action, we're only passing an object with an ID inside it and we're missing the 'updates' property.

To resolve this issue, you'll need to modify the Update interface in the following code:

export interface Update {
id: number;
updates: Contact;
}

to this code:

export interface Update {
id: number;
updates?: Contact;
}

In this instance, we've added a question mark symbol to the 'updates' property to make it optional. This resolves the TypeScript error, as we're providing the 'id' property and the 'updates' property can be disregarded.

By making this adjustment, the error in the ContactItem.tsx file will disappear.

If you check the application now, you'll notice that the delete contact feature is functioning correctly

Implementing Validations in our form

At this point, the application's CRUD (Create, Read, Update, and Delete) functionality is complete.

However, we still need to implement validation for adding or modifying contacts.

To achieve this, begin by creating a new state within the ContactForm.tsx file:

const [errorMsg, setErrorMsg] = useState('');

Here, we declare a state handle error messages

Modify the handleOnSubmit method in the ContactForm.tsx file to the following code:

  const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const {firstName, lastName, phoneNumber, address, email} = contact;
    if (
      firstName.trim() === '' ||
      lastName.trim() === '' ||
      phoneNumber.trim() === ''||
      address.trim() === ''||
      email.trim() === ''
    ) {
      setErrorMessage('All the fields are required.');
      return;
     } else if (phoneNumber.length < 3) {
      setErrorMessage('Please enter a phone number with more than 3 numbers.');
      return;
    }
    if (!dataToEdit) {
      dispatch({
        type: 'ADD_CONTACT',
        payload: {
          id: Date.now(),
          ...contact
        }
      });
      setContact({
        firstName: '',
        lastName: '',
        phoneNumber: '',
        address: '',
        email: '',
      });
      setErrorMessage('');
    } else {
      dispatch({
        type: 'UPDATE_CONTACT',
        payload: {
          id: dataToEdit.id,
          updates: {
            id: Date.now(),
            ...contact
          }
        }
      });
      toggleModal();
    }
  };
Enter fullscreen mode Exit fullscreen mode

In this handleSubmit function, extracts the firstName, lastName, and phoneNumber, address, email properties from the contact state.

We then checks if these properties are empty or contain only whitespace, and if so, we set an error message with setErrorMessage and return from it.

If the phone number is not up to 3 numbers, it sets a different error message and returns from the function.

Now, we will display the error message on the screen. To do this, add the following code inside the Form tag and before the Form.Group tag while returning the JSX:

{errorMsg && <p className='errorMsg'>{errorMsg}</p>}

Your entire ContactForm.tsx files hould now look like this:

With these modifications, we have successfully completed building our Phone Book application using React + TypeScript.

The source-code for this project can be found here

Top comments (0)