Understanding TypeScript

Jan 20, 2026 15 min

Understanding TypeScript: Building a Fitness App

Introduction

Building a full-stack application is one of the best ways to learn TypeScript. In this project walkthrough, I explored how TypeScript enhances a React fitness application, focusing on the practical aspects that make TypeScript valuable for real-world development.

Understanding TypeScript Fundamentals

The Type System Basics

TypeScript’s power comes from its ability to catch errors before runtime. Let’s start with the fundamentals:

// Basic type annotations
const selectedPage: string = "home";
const isMenuToggled: boolean = false;
const userAge: number = 25;

In practice, TypeScript often uses type inference, meaning it can figure out types automatically:

const [selectedPage, setSelectedPage] = useState("home");
// TypeScript infers selectedPage is a string

However, being explicit helps when types are complex or unclear:

const [selectedPage, setSelectedPage] = useState<string>("home");
// Now it's crystal clear this is a string state

Enums: Better Than String Constants

One of the most useful TypeScript features for navigation is enums. Instead of using plain strings that can have typos:

// Error-prone approach
setSelectedPage("home");
setSelectedPage("homee"); // Typo! No error caught

We can use enums:

enum SelectedPage {
  Home = "home",
  Benefits = "benefits",
  OurClasses = "ourclasses",
  ContactUs = "contactus"
}

// Type-safe approach
setSelectedPage(SelectedPage.Home);
setSelectedPage(SelectedPage.Homee); // TypeScript error!

Enums are special because they exist at both compile-time (for type checking) and runtime (as actual values you can use in your code). This makes them perfect for navigation states, dropdown options, and other fixed sets of values.

Typing React Components

Props and Component Signatures

Every React component with props needs a type definition:

type Props = {
  page: string;
  selectedPage: SelectedPage;
  setSelectedPage: (value: SelectedPage) => void;
};

const Link = ({ page, selectedPage, setSelectedPage }: Props) => {
  // Component logic
};

Breaking this down:

  • page: string - A simple string prop
  • selectedPage: SelectedPage - Must be one of our enum values
  • setSelectedPage: (value: SelectedPage) => void - A function that takes a SelectedPage and returns nothing (void)

Optional Props

Sometimes props aren’t always needed:

type BenefitType = {
  icon: JSX.Element;
  title: string;
  description?: string; // The ? makes this optional
  image: string;
};

The optional description can be undefined, and TypeScript won’t complain when you omit it.

Working with Arrays and Objects

Array Typing

When you have an array of objects, TypeScript needs to know the structure:

const benefits: Array<BenefitType> = [
  {
    icon: <HomeModernIcon className="h-6 w-6" />,
    title: "State of the Art Facilities",
    description: "Lorem ipsum dolor sit amet",
    image: image1
  },
  // More items...
];

This tells TypeScript: “This is an array where every item matches the BenefitType structure.”

Mapping with Types

When mapping over arrays, TypeScript knows the type of each item:

benefits.map((benefit: BenefitType) => (
  <Benefit
    key={benefit.title}
    icon={benefit.icon}
    title={benefit.title}
    description={benefit.description}
  />
));

The benefit: BenefitType annotation is optional here (TypeScript can infer it), but it helps with IntelliSense and clarity.

Advanced: Type Assertions

Sometimes you know more about a type than TypeScript does:

const lowerCasePage = page.toLowerCase().replace(/ /g, "");
// TypeScript doesn't know this creates a valid SelectedPage

onClick={() => setSelectedPage(lowerCasePage as SelectedPage)}
// We tell TypeScript: "Trust me, this IS a SelectedPage"

The as keyword is a type assertion. Use it sparingly and only when you’re certain about the type. Overusing it defeats the purpose of TypeScript.

Interfaces vs Types

You’ll see both interface and type in TypeScript code:

// Using interface
interface BenefitType {
  icon: JSX.Element;
  title: string;
}

// Using type
type BenefitType = {
  icon: JSX.Element;
  title: string;
};

For most cases, they’re interchangeable. The key differences:

  • Interfaces can be extended and merged
  • Types can represent unions and more complex types
  • Choose one approach and stay consistent in your project

Practical TypeScript Patterns

1. Shared Type Definitions

Create a central file for reusable types:

// shared/types.ts
export enum SelectedPage {
  Home = "home",
  Benefits = "benefits",
  OurClasses = "ourclasses",
  ContactUs = "contactus"
}

export interface BenefitType {
  icon: JSX.Element;
  title: string;
  description?: string;
  image: string;
}

export interface ClassType {
  name: string;
  description?: string;
  image: string;
}

2. Function Typing

Functions that don’t return values use void:

type Props = {
  setSelectedPage: (value: SelectedPage) => void;
};

Functions with return values specify the type:

const calculateAge = (birthYear: number): number => {
  return new Date().getFullYear() - birthYear;
};

3. React Hook Form with TypeScript

Form libraries work beautifully with TypeScript:

const {
  register,
  trigger,
  formState: { errors }
} = useForm();

<input
  {...register("email", {
    required: true,
    pattern: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
  })}
/>

{errors.email?.type === "required" && (
  <p>This field is required.</p>
)}

TypeScript ensures you’re accessing form properties correctly and catches typos in field names.

Common TypeScript Pitfalls

1. The any Escape Hatch

const handleSubmit = async (e: any) => {
  // Using 'any' turns off type checking
};

While sometimes necessary (like ambiguous event types), overusing any defeats TypeScript’s purpose. Use it only when the time spent figuring out the exact type isn’t worth the benefit.

2. Forgetting to Import Types

import { BenefitType } from "@/shared/types"; // ✅ Import the type

Unlike runtime code, TypeScript types need explicit imports from their definition files.

3. Compile Time vs Runtime

TypeScript types don’t exist in your final JavaScript bundle. They’re only for development:

// This code doesn't exist after compilation
type Props = { name: string };

// Enums are an exception - they DO exist at runtime
enum SelectedPage { Home = "home" }

Why TypeScript Matters

1. IntelliSense and Autocomplete

Your editor knows what properties exist:

benefit. // Your editor suggests: icon, title, description, image

2. Refactoring Confidence

Change a type definition, and TypeScript shows every place that needs updating. No more hunting through files for where you used a particular property name.

3. Self-Documenting Code

Types serve as inline documentation:

setSelectedPage: (value: SelectedPage) => void
// Anyone reading this knows exactly what it expects

4. Catching Bugs Early

<Benefit
  title={benefit.titel} // TypeScript error! Did you mean 'title'?
/>

Conclusion

TypeScript transforms JavaScript from a language where errors hide until runtime into one where many mistakes are caught while you type. The initial learning curve pays dividends through:

  • Fewer bugs in production
  • Faster development with better tooling
  • Easier collaboration on large codebases
  • More maintainable code

Start small: add types to function parameters, define prop types for components, and use enums for string constants. As you grow comfortable, explore advanced features like generics, utility types, and conditional types

~Sheetal Naik