DEV Community

Savannah TechStack
Savannah TechStack

Posted on

TypeScript with React Tutorial

React TypeScript Tutorial

Introduction

TypeScript has grown to be one of the most popular and widely used programming language in web development. TypeScript is a superset of JavaScript. That simply means TypeScript is essentially JavaScript with some additional features. The goal of TypeScript is to write strongly typed JavaScript. Strong typing helps to scale web applications with minimal bugs. TypeScript code is converted to JavaScript by use of a compiler like TypeScript Compiler or Babel.

For a better understanding, we will develop a Student Application using React and TypeScript. This tutorial will give you everything you need to get started with TypeScript in your next project.

If you would prefer to follow this tutorial on YouTube it's available at the link below.

The final solution is available on GitHub

GitHub logo tndungu / React-TypeScript-StudentApp

React TypeScript Student App

React TypeScript Student App

A Student App using React TypeScript. Includes features like Type Inference, Type Annotation, Union Types, Interfaces, Enums and Generics.

Local Setup

  1. Clone the Repository using the following command: git clone https://github.com/tndungu/React-TypeScript-StudentApp.git
  2. Open the Repository using your favorite text editor. I use Visual Studio Code as a personal preference.
  3. Open terminal and run the following: npm install
  4. Run the project using npm start. This will open the project in http://localhost:3000

Video

There is a step by step guide on building the project on YouTube.

React TypeScript Student App




Prerequisites

This tutorial assumes you have some basic knowledge of React

Why use TypeScript?

There are many benefits of using typescript. The main ones are listed below:

  • Strong typing ensures bugs are caught during development as opposed to being caught while the application is in Production. Also makes it easy to debug code.
  • Documentation - It serves as documentation for JavaScript code making it easy to read and maintain.
  • Saves development time. 
  • Generics in TypeScript provides a powerful type system that gives developers a lot of flexibility.

Student App in TypeScript

We will build an app using React that will cover the following aspects of TypeScript.

  • Props 
  • Type inference vs Type annotation
  • Union Types
  • Organizing interfaces
  • Enums 
  • Generics

App Development: Step by Step Guide

To start a new typescript app, use the following command

  • yarn:
yarn create-react-app student-app --template typescript
Enter fullscreen mode Exit fullscreen mode
  • npm:
npx create-react-app student-app --template typescript
Enter fullscreen mode Exit fullscreen mode

cd into student-app and yarn start OR npm start if using npm.

Props

We will start by passing a prop to the <App/> component. It will be a string that will have the name of the app. Here we will see our first use case for TypeScript.
Modify the App.tsx and index.tsx files to look as below. Delete the App.test.tsx file.

//Index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App name="Student App" />
  </React.StrictMode>
);

//App.tsx
export interface AppName {
  name: string;
}

function App({ name }: AppName) {
  return (
    <div className="App">
      <h1>{name}</h1>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

On your browser, you should be able to see Student App displayed. As we can see, AppName is an Interface that has a property called name. 
An Interface is an abstract type that the compiler uses to know which property names a given object can have. It's used for type checking. 
In the AppName Interface, the property name is a string, that's why we are passing a string to the App component. If you try to pass any other type like integer or boolean the App will give an error. It will not even compile. If you were not using TypeScript, the app will compile but give unexpected results later after deployment. 

Type inference vs Type annotation

const [studentId,setStudentId] = useState(0)

setStudentId('test')
Enter fullscreen mode Exit fullscreen mode

If you try the code above, it will not compile, typescript will give an error. This is because typescript has already inferred the type of studentId to be an integer(number). If you try to assign a string to studentId will therefore generate an error at compile time. This is referred to as type inference in TypeScript. 

Similarly, the code below will not run in TypeScript.

const [student, setStudent] = useState(null)

setStudent({
  name: 'Antony',
  surname: 'Ndungu',
  age: 15
})
Enter fullscreen mode Exit fullscreen mode

This is because TypeScript infers the student object to be of type null and therefore we have to explicitly define the student object and in the useState() hook we have to tell TypeScript user can be either null or student object. This is referred to as Type annotation. We will do that using the Student interface. The final code of our App.tsx will look as follows:

import { useState } from "react";
import './App.css'

export interface AppName {
  name: string;
}

export interface Student {
  name: string;
  surname: string;
  age?: number;
  address?: {
    houseNo: number;
    street: string;
    Town: string;
  }
}

function App({ name }: AppName) {
  const [student, setStudent] = useState<Student | null>(null)

  const addStudent = () => {
    setStudent({
      name: 'Antony',
      surname: 'Ndungu',
      age: 20
    })
  }

  return (
    <div className="App">
      <h1>{name}</h1>
      <p><b>{student?.name} {student?.surname}</b></p>
      <button onClick={addStudent}> Add Student</button>
    </div>
  );
}
export default App;

Enter fullscreen mode Exit fullscreen mode

From the above code, the student can either be null or Student object. This is denoted by the code useState<Student | null>(null). This introduces another concept called Union Types.

Union Types

This is when you have an object that can be of having different types. For instance you might have const [student, setStudent] = useState<Student | null | boolean>(null). In this case Student | null | boolean are Union Types.

Organizing Interfaces

There are 2 issues as far as our interfaces are concerned:

  • We should not nest objects as we have done in the Student interface. Instead, we should have another interface for Address.
  • The Interfaces should be on their separate module for ease of maintenance and re-use. 

We will create a new Interface for Address. We will then create a new module for interfaces by creating an interfaces.ts file inside the src folder and moving the interfaces there. We will then import our interfaces in the App.tsx file. The final App.tsx and Interfaces.ts files will look as follows:

//App.tsx
import { useState } from "react";
import './App.css'
import { Student, AppName } from './interfaces'

function App({ name }: AppName) {
  const [student, setStudent] = useState<Student | null>(null)

  const addStudent = () => {
    setStudent({
      name: 'Antony',
      surname: 'Ndungu',
      age: 20
    })
  }

  return (
    <div className="App">
      <h1>{name}</h1>
      <p><b>{student?.name} {student?.surname}</b></p>
      <button onClick={addStudent}> Add Student</button>
    </div>
  );
}

export default App;

//interfaces.tsx
export interface AppName {
  name: string;
}

export interface Address {
  houseNo: number;
  street: string;
  Town: string;
}

export interface Student {
  name: string;
  surname: string;
  age?: number;
  address?: Address
}

Enter fullscreen mode Exit fullscreen mode

Enums

An Enum is a type for holding constant values. In our example, the student level can either be "Undergraduate" or "Postgraduate".

export enum Level {
  Undergraduate = "Undergraduate",
  Postgraduate = "Postgraduate"
}

Enter fullscreen mode Exit fullscreen mode

The above enum can be used to conditionally display the age of a student based on the student's level as shown below:

{
  student?.level === Level.Undergraduate &&
  <p><b>Age: {student.age}</b></p>
}
Enter fullscreen mode Exit fullscreen mode

Generics

Generics are an important feature of TypeScript that is used for creating reusable components. The same component can be used to handle different data types as shown below. 

Display both Students' and Courses' lists using the same component.

TypeScript Student Courses Lists Display

For our student App, I would like to display 2 lists: One for the students' list and another one for the courses' list. Without generics, I will end up creating 2 components that will be used to display the 2 lists. However, with Generics I will use only one component to display both lists. The DisplayData component can be re-used to display any list of items even as our app grows bigger.

In src folder, I have created DisplayData.tsx component. The file looks as follows:

interface Item {
  id: number;
}

interface DisplayDataItem<T> {
  items: Array<T>
}

export const DisplayData = <T extends Item>({ items }: DisplayDataItem<T>) => {
  return (
    <>
      <ul>
        {items.map((item) => (
          <li key={item.id}>{JSON.stringify(item)}</li>
        ))}
      </ul>
    </>
  )
}

Enter fullscreen mode Exit fullscreen mode

Interface Item has a property id which means any object that uses this component must have an id property. Interface DisplayDataItem<T> is an object that represents an Array<T> of type T which means it can be used by any object which consists of an array of items. DisplayData is a function that accepts an array of items and displays the list. 
The following is the final code for App.tsx, App.css and data.ts files.


//App.tsx
import { useState } from "react";
import './App.css'
import { Student, AppName, Level } from './interfaces'
import { studentList, coursesList } from "./data";
import { DisplayData } from "./DisplayData";

function App({ name }: AppName) {
  const [student, setStudent] = useState<Student | null>(null)

  const addStudent = () => {
    setStudent({
      name: 'Antony',
      surname: 'Ndungu',
      age: 20,
      level: "Undergraduate"
    })
  }

  return (
    <div className="App">
      <h1>{name}</h1>
      <p><b>{student?.name} {student?.surname}</b></p>
      {student?.level === Level.Undergraduate &&
        <p><b>Age: {student.age}</b></p>
      }
      <button onClick={addStudent}> Add Student</button>
      <h3>List of Students</h3>
      <div>
        <DisplayData items={studentList} />
      </div>
      <h3>List of Courses</h3>
      <div>
        <DisplayData items={coursesList} />
      </div>
    </div>
  );
}

export default App;

//data.ts
export const studentList = [
  { id: 1, name: 'Antony', surname: 'Ndungu', level: 'Undergraduate', age: 20 },
  { id: 2, name: 'Chanelle', surname: 'John', level: 'Postgraduate', age: 50 },
  { id: 3, name: 'Ian', surname: 'Smith', level: 'Undergraduate', age: 46 },
  { id: 4, name: 'Michael', surname: 'Starke', level: 'Postgraduate', age: 64 },
  { id: 5, name: 'Chris', surname: 'De Kock', level: 'Undergraduate', age: 19 },
]

export const coursesList = [
  { id: 1, code: 'A141', name: 'Algorithms Analysis', description: 'Analysis & Design' },
  { id: 1, code: 'BN445', name: 'Computer Architecture I', description: 'Computer Architecture' },
  { id: 1, code: 'P888', name: 'Operations Research', description: 'Maths - Operations Research' },
  { id: 1, code: 'Z9989', name: 'Discrete Maths', description: 'Discrete Mathematics' }
]

.App {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}

li{
  list-style-type: none;
}

button {
  height: 30px;
  width: 150px;
  background-color: turquoise;
  border-radius: 5px;
}

Enter fullscreen mode Exit fullscreen mode

Generic Search Function

Finally, we will add a Generic search where the student list can be sorted based on either student name or Age on a button click. 
Create a GenericSort.ts file and ensure you have the following code. This code takes a list of array items and key for sorting then returns the sorted list. For example, if I would like to sort the student's list based on student name I will call the function GenericSort(studentList,"name")
This is a powerful use case for generics, I can use it if I want to sort the student records list based on different sorting columns. Implementing this without TypeScript would end up with many functions that are difficult to extend.

//GenericSort
export const GenericSort = <T,>(items: Array<T>, key: keyof T) => {

  items.sort((a, b) => {
    if (a[key] > b[key]) {
      return 1;
    }
    if (a[key] < b[key]) {
      return -1;
    }
    return 0;
  })
  return items
}
Enter fullscreen mode Exit fullscreen mode
//App.tsx
import { useState } from "react";
import './App.css'
import { Student, AppName, Level } from './interfaces'
import { studentList, coursesList } from "./data";
import { DisplayData } from "./DisplayData";
import { GenericSort } from "./GenericSort";

function App({ name }: AppName) {
  const [student, setStudent] = useState<Student | null>(null)
  const [list, setList] = useState(studentList)

  const addStudent = () => {

    setStudent({
      name: 'Antony',
      surname: 'Ndungu',
      age: 20,
      level: "Undergraduate"
    })
  }

  const sortData = () => {

    GenericSort(studentList, "age")
    setList([...studentList])
  }
  return (
    <div className="App">
      <h1>{name}</h1>
      <p><b>{student?.name} {student?.surname}</b></p>
      {student?.level === Level.Undergraduate &&
        <p><b>Age: {student.age}</b></p>
      }
      <button onClick={addStudent}> Add Student</button>
      <br />
      <button onClick={sortData}>Sort Data</button>
      <h3>List of Students</h3>
      <div>
        <DisplayData items={list} />
      </div>
      <h3>List of Courses</h3>
      <div>
        <DisplayData items={coursesList} />
      </div>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Conclusion

Awesome! at this point, you have gone through the core building blocks of building a React App using TypeScript. In the final part of our project, we went through an introduction to some advanced features of TypeScript, Generics. 
Happy coding with TypeScript.
Feel free to comment below in case you need further assistance.

Discussion (5)

Collapse
lukeshiru profile image
Luke Shiru

Just a heads up that you can add highlighting to the code blocks if you'd like. Just change:

code block with no colors example

... to specify the language:

code block with colors example

More details in our editor guide!

Collapse
tndungu profile image
Savannah TechStack Author

Thanks for the tip, a cool feature I hadn't used before.

Collapse
andrewbaisden profile image
Andrew Baisden

Great guide.

Collapse
yongchanghe profile image
Yongchang He

Thank you for sharing!

Collapse
tndungu profile image
Savannah TechStack Author

You're welcome.