Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Learn TypeScript with React By Building a CRUD Application

TypeScript is a powerful programming language that enhances the development of complex web applications by adding a static type system to JavaScript. React is a popular JavaScript library used for building user interfaces. Combining these two technologies can take your web development to the next level. 

In this article, you will learn the basics of TypeScript with React in the first sections of the article. And then you will learn to build a contact manager app using React and TypeScript.

So by the end of this article, you will have a clear understanding of how to properly use TypeScript with React. If you want to refer the code for this entire project, you can download it from here.

So let’s get started.

React And TypeScript Basics

When learning React with TypeScript, the first thing you should understand is the file extension.

Every React + TypesSript file need to have a .tsx extension.

If the file does not contain any JSX-specific code, then you can use the .ts extension instead of the .tsx extension.

To create a component in React with TypeScript, you can use the FC type from the react package and use it after the component name.

Create a new file with the name Header.tsx and add the following code:

import { FC } from 'react'; const Header: FC = () => { return

Welcome, Mike!/h1>; }; export default Header;

Code language: TypeScript (typescript)

Here, we’re just displaying a welcome message in a functional component.

Now, If we want to pass a dynamic name as a prop we can do that like this:

import { FC } from 'react'; interface HeaderProps { name: string; } const Header: FC= ({ name }) => { return

Welcome, {name}!/h4>; }; export default Header;

Code language: TypeScript (typescript)

Here, we have declared HeaderProps as an interface that has a name property and for the Header component, we have added angle brackets to use that interface.

This way TypeScript will know that the Header component needs a name prop which is a required prop.

And we can call the component like this:

'Jerry' />
Code language: TypeScript (typescript)

If you miss any of the required props like name in our case, then TypeScript will throw an error as can be seen below:

So whatever props the component interface is designed to take has to be provided unless they’re optional.

If some of the properties are optional and not required all the time then we can use the ? operator to indicate that it’s an optional prop like this:

interface HeaderProps { name: string; isMarried?: boolean; }
Code language: TypeScript (typescript)

Here, isMarried is made an optional prop because of the ? at the end and we can use it like this:

const Header: FC= ({ name, isMarried }) => { return (

Welcome, {name}!/h4>

Marital status: {isMarried ? 'Married' : 'Not Provided'}p> /> ); };

Code language: TypeScript (typescript)

So, If the isMarried prop is provided, you will see the Married status otherwise Not Provided status as shown below:

In the above code, we have used an interface to declare the type of props passed.

But you can also create a type as shown below:

type HeaderProps = { name: string, isMarried?: boolean };
Code language: TypeScript (typescript)

And use it the same way as used for the interface.

It’s up to you which one to choose, however in most of the code, you will see the interface used instead of the type.

Alternative Way of Declaring Component Props Types

In the above code, we have used : FC to specify the props types.

There is another way you will find in some of the React + TypeScript codebases.

So instead of declaring a Header component like this:

const Header: FC= ({ name, isMarried }) => { // ... };
Code language: TypeScript (typescript)

we can declare it like this:

const Header = ({ name, isMarried }: HeaderProps) => { // ... };
Code language: TypeScript (typescript)

As you can see, we have specified the TypeScript type while destructuring the props.

How to Work With Event Handlers

It’s common to have some event handlers in the component for events like click event, change event, etc.

So let’s see how to work with them when using TypeScript.

Take a look at the below code:

import { FC, useState } from 'react'; const UserSearch: FC = () => { const [searchTerm, setSearchTerm] = useState(''); const handleSearch = (event) => {}; return ( type='text' name='searchTerm' value={searchTerm} placeholder='Type something!' onChange={handleSearch} /> {searchTerm &&
SearchTerm: {searchTerm}/div>}
> /> ); }; export default UserSearch;
Code language: TypeScript (typescript)

Here, we’re displaying an input field where users enter something and we’re displaying that value below that input.

But as you can see, we’ve not specified any type for the `event` parameter of the handleSearch function which we explicitly need to specify otherwise we will get a TypeScript error as shown below:

As you can see in the above screenshot, when the mouse hovered over the event parameter, TypeScript gives an error saying, “Parameter event implicitly has an any type.”

To fix that, we first need to identify the exact type of event.

To do that, temporarily change the below code:

onChange = { handleSearch };
Code language: TypeScript (typescript)

to this code:

onChange = { (event) => {} };
Code language: TypeScript (typescript)

Here, we’re using an inline function so, If you mouse over the event parameter, you will see the event type `React.ChangeEvent` which we can use for the event parameter as shown below:

So by just adding an inline function, you can easily identify the event type of any event handler added.

Now, as we got the event type, we can set the searchTerm value using the setSearchTerm function as shown below:

const handleSearch = (event: React.ChangeEventHTMLInputElement>) => { setSearchTerm(event.target.value); };
Code language: TypeScript (typescript)

And the search is working as shown below:

Now, you know the basics of React + TypeScript, let’s build a contact manager app so will you get hands-on experience with React + TypeScript.

How to Setup a TypeScript + React Project

**You can see the final demo of the application we’re building here

To set up the app we will be using Vite. It’s a popular and faster alternative to create-react-app.

We’ll use Vite because create-react-app becomes slow when the application grows and takes a lot of time to refresh the page when we make any changes in the application code. Also, by default, it adds a lot of extra packages which we rarely need.

Vite just rebuilds the things we changed, instead of rebuilding the entire application which saves a lot of time during development.

Keep in mind that Vite requires Node.js version 14.18+, so make sure to install a Node version greater than or equal to 14.18.

The easiest and simplest way to install and switch Node.js versions is to use [nvm].

Even if you’re using create-react-app, all the code you will learn in this tutorial should run exactly the same without any errors.

To create a new Vite project with React and TypeScript, execute the npm init vite command from the terminal.

It will ask you for the project name, framework, and variant.

– For project name, you can enter contact-manager-app-typescript or any name of your choice.

– For framework, select React from the list of options

– For variant, select TypeScript from the list of options

Once the project is created, you can open that project in your favorite IDE like Visual Studio Code.

The project folder structure will look like this:

Now, execute the yarn install or npm install command from inside the project folder to install all the packages from the package.json file.

Once all the packages are installed, you can execute the yarn run dev or npm run dev command to start the created React application.

As you can see the application can be accessed on the URL http://localhost:5173/.

Initial Project Setup

Install the bootstrap, react-bootstrap and react-icons npm packages by executing the following command from the project folder:

yarn add bootstrap@5.2.3 react-bootstrap@2.7.0 react-icons@4.7.1
Code language: TypeScript (typescript)

or with npm:

npm install bootstrap@5.2.3 react-bootstrap@2.7.0 react-icons@4.7.1
Code language: TypeScript (typescript)

Here, we’re installing the latest and specific versions of packages so you will not have any issues running the application If in the future there is a newer version of any of the packages.

Now, open the index.css file and add the following contents inside it:

* { padding: 0; margin: 0; box-sizing: border-box; } body { font-family: Inter, sans-serif; padding: 1rem; letter-spacing: 1px; background-color: #f7f6f9; color: #7b774e; } .main-container { margin: 1rem auto; display: flex; justify-content: start; align-items: baseline; } .btn, .btn:active, .btn:hover { border: none; background-color: #3b8855d6; } .icon { cursor: pointer; } .errorMsg { color: #f21e08; background: #fff0f0; padding: 10px; } h1 { text-align: center; } .contact-form { margin: 1rem auto; width: 45%; } .modal-dialog .contact-form { width: 100%; } .submit-btn { margin-top: 1rem; letter-spacing: 1px; } .contacts-list-table-container { max-width: 100%; height: 500px; overflow: auto; } .contacts-list { display: flex; width: 45%; flex-direction: column; justify-content: flex-start; align-items: center; } .contacts-list-title { margin-bottom: 1.2rem; } .contacts-list-table, .contacts-list-table tr, .contacts-list-table th, .contacts-list-table td { padding: 5px 20px; } .contacts-list-table { border-collapse: collapse; width: 100%; } .contacts-list-table th, .contacts-list-table td { border: 1px solid #dddddd; text-align: left; padding: 8px; } .contacts-list-table tr:nth-child(even) { background: #ecebee; } .contacts-list-header { position: sticky; top: 0; background: #ecebee; } @media screen and (max-width: 900px) { .contact-form, .contacts-list, .main-container { flex-direction: column; align-items: center; width: 100%; } }
Code language: CSS (css)

Now, open the main.tsx file and add the bootstrap CSS import before the index.css import as shown below:

import 'bootstrap/dist/css/bootstrap.min.css'; import './index.css'
Code language: TypeScript (typescript)

How to Create the Initial Pages

Now, create a components folder inside the src folder and create a Header.tsx file inside it.

import { FC } from 'react'; const Header: FC = () => { return (

Contact Manager App/h1> header> ); }; export default Header;

Code language: TypeScript (typescript)

Here, we have declared the Header component using FC which is a TypeScript way of declaring a component as a functional component.

If you want, you can skip the : FC as the code will work without mentioning it also but it’s always good to specify it explicitly.

Now, Open the App.tsx file and replace it with the following contents:

import Header from './components/Header'; function App() { return (
'App'> /div> ); } export default App;
Code language: TypeScript (typescript)

Now, If you start the application using yarn run dev or npm run dev and access it at URL http://localhost:5173/, you will see the following screen:

If you want, you can open the index.html file and change the title of the page to Contact Manager App instead of the Vite + React + TS.

Now, create a new file ContactForm.tsx inside the components folder and add the following code inside it:

import { FC, useState } from 'react'; import { Button, Form } from 'react-bootstrap'; const ContactForm: FC = () => { const [contact, setContact] = useState({ firstName: '', lastName: '', phone: '' }); const handleOnChange = (event) => { const { name, value } = event.target; setContact((prevState) => { return { ...prevState, [name]: value }; }); }; const handleOnSubmit = (event) => { event.preventDefault(); }; return (
'contact-form'>
'firstName'> First Name/Form.Label> > /Form.Group> Last NameForm.Label> 'lastName' name='lastName' value={contact.lastName} type='text' onChange={handleOnChange} /> /Form.Group> PhoneForm.Label> 'phone' name='phone' value={contact.phone} type='number' onChange={handleOnChange} /> /Form.Group>
Code language: TypeScript (typescript)

In the above code, we’re displaying a form with three input fields namely, first name, last name, phone, and a submit button.

For displaying the inputs, we’re using the Form.Control component from react-bootstrap so the UI will look nice and we don’t need to write a lot of CSS ourselves.

All the above code is a pure React code without any TypeScript Code, so you will see red underlines for the event parameter of the handleOnChange and handleOnSubmit methods as can be seen in the below screenshot:

As you can see in the above screenshot, when you mouse hover over the event parameter, you can see the TypeScript error saying, “Parameter event implicitly has an any type.”

To fix that, we first need to identify the exact type of event we need to provide.

To do that, temporarily change the below code:

onChange = { handleOnChange };
Code language: TypeScript (typescript)

to this code:

onChange = { (event) => {} };
Code language: TypeScript (typescript)

Here, we’re using an inline function so, if you mouse over the event parameter, you will see the event type React.ChangeEvent which we can use for the event parameter as shown below:

However, as we know, the element for which the onChange handler is added is an input element so instead of React.ChangeEvent, we can use React.ChangeEvent so we don’t need to import any extra TypeScript specific type.

With this change, you can see the TypeScript error is gone.

Similarly, we can find out the event parameter type for the onSubmit handler as shown below:

Event Type Submit

So using this simple way, we can find out the event type of any change or submit handler function.

Now, you can revert the onChange and onSubmit handlers from inline functions to the respective handler function:

onChange = { handleOnChange }; onSubmit = { handleOnSubmit };
Code language: TypeScript (typescript)

Now, open the App.tsx file, and let’s add the ContactForm component below the Header component.

So your App.tsx file will look like this:

import ContactForm from './components/ContactForm'; import Header from './components/Header'; function App() { return (
'App'>
'main-container'> /div> div> ); } export default App;
Code language: TypeScript (typescript)

And If you check the application, you will see the following screen:

Let’s add a console.log statement in the handleOnSubmit method as shown below so we can see the data submitted.

const handleOnSubmit = (event: React.FormEventHTMLFormElement>) => { event.preventDefault(); console.log(contact); };
Code language: TypeScript (typescript)

As you can see, we’re correctly able to store the details in the state with the name contact and display it on the console.

How to Use useReducer Hook For Storing Contacts

Now, we need to display the added contacts on the page.

To do that, we first need to store all the contacts together.

So, we will use the useReducer hook for that using which we can easily handle the edit and delete contact functionality.

If you’re not aware of the useReducer hook, then check out this article.

Create a new folder with the name reducer inside the src folder and create a contactsReducer.ts file inside it with the following contents:

export const contactsReducer = (state, action) => { switch (action.type) { case 'ADD_CONTACT': return { ...state, contacts: [...state.contacts, action.payload] }; default: return state; } };
Code language: TypeScript (typescript)

Note that the file extension is .ts and not .tsx because there is no JSX code inside it.

When you save the file, you will see a red underline for the state and action parameters as shown below:

So let’s define the TypeScript types for the state and action parameters as shown below:

export interface Contact { firstName: string; lastName: string; phone: string; } export interface Action { type: 'ADD_CONTACT' payload: Contact; } export interface State { contacts: Contact[]; } export const contactsReducer = (state: State, action: Action): State => { switch (action.type) { case 'ADD_CONTACT': return { ...state, contacts: [...state.contacts, action.payload] }; default: return state; } };
Code language: TypeScript (typescript)

In the above code, we’re also explicitly defining State as the return type of the contactsReducer function as can be seen after the arrow syntax (=>):

export const contactsReducer = (state: State, action: Action): State => {
Code language: TypeScript (typescript)

If you have worked with redux before, you might know that action always contains a type property and an optional payload.

So we have defined an Action interface to indicate the action type.

As the contact form contains the firstName, lastName, and phone properties, we’re declaring an interface Contact indicating that it will be the type of payload.

We’re also exporting those interfaces so we can use them in other files if required.

Also, we have defined state as an object with a contacts property.

The contacts property will be an array containing only the firstName, lastName, and phone properties so we have defined it as shown below:

export interface State { contacts: Contact[]; }
Code language: TypeScript (typescript)

So the contacts property will always contain an array of objects of the Contact type.

Now, open the App.tsx file and use the contactsReducer as the first argument for the useReducer hook as shown below:

import { useReducer } from 'react'; import ContactForm from './components/ContactForm'; import Header from './components/Header'; import { contactsReducer, State } from './reducer/contactsReducer'; const initialState: State = { contacts: [] }; function App() { const [state, dispatch] = useReducer(contactsReducer, initialState); return (
'App'>
'main-container'> /div> div> ); } export default App;
Code language: TypeScri


This post first appeared on Mobisoft Infotech, please read the originial post: here

Share the post

Learn TypeScript with React By Building a CRUD Application

×

Subscribe to Mobisoft Infotech

Get updates delivered right to your inbox!

Thank you for your subscription

×