DEV Community

Cover image for Beginner's Guide To TypeScript + React Integration
Yogesh Chavan
Yogesh Chavan

Posted on

Beginner's Guide To TypeScript + React Integration

Introduction to TypeScript

What is TypeScript?

TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. Developed and maintained by Microsoft, TypeScript adds optional static typing and class-based object-oriented programming to JavaScript. Since TypeScript is a superset of JavaScript, existing JavaScript programs are also valid TypeScript programs.

TypeScript code cannot run directly in browsers or Node.js - it must first be compiled (transpiled) to JavaScript. This compilation step is where TypeScript catches type errors, helping you find bugs before your code runs.

Why TypeScript over JavaScript?

While JavaScript is powerful and flexible, it has limitations when building large-scale applications. TypeScript addresses these limitations:

  • Static Type Checking - Catch errors at compile time rather than runtime
  • Better IDE Support - Enhanced autocompletion, navigation, and refactoring
  • Improved Code Readability - Type annotations serve as documentation
  • Enhanced Refactoring - Safely rename symbols and restructure code
  • Latest ECMAScript Features - Use modern features with backward compatibility

TypeScript vs JavaScript

Feature JavaScript TypeScript
Type System Dynamic typing Static typing (optional)
Compilation Interpreted directly Compiles to JavaScript
Error Detection Runtime errors Compile-time errors
IDE Support Basic Enhanced (IntelliSense)
Interfaces Not supported Fully supported
Generics Not supported Fully supported

Installing TypeScript

Step 1: Install Node.js

Before using TypeScript, you need Node.js installed on your machine. Follow these steps:

  1. Visit the official Node.js website: https://nodejs.org
  2. Download the LTS (Long Term Support) version for your operating system
  3. Run the installer and follow the installation wizard
  4. Verify the installation by opening a terminal and running:
node --version
# Should output: v20.x.x or higher

npm --version
# Should output: 10.x.x or higher
Enter fullscreen mode Exit fullscreen mode

Step 2: Install TypeScript Globally

Once Node.js is installed, you can install TypeScript globally using npm:

npm install -g typescript
Enter fullscreen mode Exit fullscreen mode

Step 3: Verify TypeScript Installation

tsc --version
# Should output: Version 5.x.x
Enter fullscreen mode Exit fullscreen mode

Tip: You can also install TypeScript locally in a project using npm install typescript --save-dev. This is recommended for projects to ensure consistent versions across team members.

Setting up TypeScript with Node.js

To set up a new TypeScript project, create a directory and initialize it:

mkdir my-typescript-project
cd my-typescript-project
npm init -y
npm install typescript --save-dev
Enter fullscreen mode Exit fullscreen mode

Create a TypeScript configuration file (tsconfig.json):

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

Here's a recommended configuration for beginners:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
Enter fullscreen mode Exit fullscreen mode

Your First TypeScript Program

Create a file called hello.ts in your src folder:

// src/hello.ts
function greet(name: string): string {
  return "Hello, " + name + "!";
}

const message: string = greet("TypeScript");
console.log(message);
Enter fullscreen mode Exit fullscreen mode

Notice the type annotations: name: string specifies the parameter type, and : string after the parentheses specifies the return type.

Understanding the Compilation Process

TypeScript must be compiled to JavaScript before it can run:

# Compile a single file
tsc src/hello.ts

# Compile using tsconfig.json
tsc

# Watch mode - recompile on changes
tsc --watch
Enter fullscreen mode Exit fullscreen mode

When you run the tsc command (or tsc --watch for continuous compilation), TypeScript creates a new dist folder containing the compiled JavaScript files. You'll see your hello.ts file transformed into hello.js with all type annotations removed:

// dist/hello.js (compiled output)
"use strict";
function greet(name) {
  return 'Hello, ' + name + '!';
}
const message = greet('TypeScript');
console.log(message);
Enter fullscreen mode Exit fullscreen mode

Here's what the project structure looks like in VS Code after compilation:

Project Structure

Note: All type annotations are removed during compilation. Types only exist at development and compile time - they help catch errors but don't affect runtime behavior.

Running the Compiled Code

Now that your TypeScript code has been compiled to JavaScript, you can run it using Node.js. Execute the compiled JavaScript file from the dist folder:

# Run the compiled JavaScript file
node dist/hello.js
Enter fullscreen mode Exit fullscreen mode

You should see the following output in your terminal:

Hello, TypeScript!
Enter fullscreen mode Exit fullscreen mode

Congratulations! You've successfully written, compiled, and executed your first TypeScript program. The workflow is: write .ts files → compile with tsc → run the output .js files with node.

Tip: You can also use ts-node to run TypeScript files directly without manual compilation: npm install -g ts-node, then run ts-node src/hello.ts. This is useful during development.


TypeScript Basics

Basic Types (string, number, boolean)

TypeScript provides several basic types that form the foundation of the type system:

// String type
let firstName: string = "John";
let lastName: string = 'Doe';

// Number type (includes integers and floats)
let age: number = 30;
let price: number = 99.99;
let hex: number = 0xf00d;

// Boolean type
let isActive: boolean = true;
let isCompleted: boolean = false;
Enter fullscreen mode Exit fullscreen mode

Type Inference

TypeScript can automatically infer types based on assigned values:

// TypeScript infers the type automatically
let name = "Alice";    // inferred as string
let count = 42;        // inferred as number
let isValid = true;    // inferred as boolean

// Type is locked after inference
name = "Bob";   // OK
name = 123;     // Error: Type 'number' is not assignable to type 'string'
Enter fullscreen mode Exit fullscreen mode

Best Practice: Use explicit types when the initial value doesn't clearly indicate the intended type, or when declaring variables without immediate initialization.

Arrays and Tuples

TypeScript provides two ways to define arrays, and introduces tuples for fixed-length arrays:

// Arrays - two syntax options
let numbers: number[] = [1, 2, 3, 4, 5];
let names: Array<string> = ["Alice", "Bob", "Charlie"];

// Mixed arrays
let mixed: (string | number)[] = [1, "two", 3];

// Tuples - fixed length with specific types
let person: [string, number] = ["Alice", 30];
let coordinate: [number, number, number] = [10, 20, 30];

// Accessing tuple elements
console.log(person[0]);  // "Alice" (string)
console.log(person[1]);  // 30 (number)
Enter fullscreen mode Exit fullscreen mode

Enums

Enums define a set of named constants, making code more readable:

// Numeric enum (auto-increments from 0)
enum Direction {
  Up,     // 0
  Down,   // 1
  Left,   // 2
  Right   // 3
}

// Numeric enum with custom values
enum StatusCode {
  OK = 200,
  BadRequest = 400,
  NotFound = 404,
  ServerError = 500
}

// String enum
enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}

// Using enums
let direction: Direction = Direction.Up;
let status: StatusCode = StatusCode.OK;
Enter fullscreen mode Exit fullscreen mode

Using 'as const' Instead of Enums

While enums are useful, modern TypeScript development often favors using as const assertions instead. The as const assertion tells TypeScript to infer the most specific type possible, making values readonly and literal types.

Why use 'as const' over enums?

  • Tree-shaking friendly - Regular objects with 'as const' can be tree-shaken by bundlers, while enums cannot
  • No runtime overhead - 'as const' objects don't generate extra JavaScript code like enums do
  • Better type inference - Works seamlessly with TypeScript's type system
  • Simpler JavaScript output - The compiled code is just a plain object
// Using 'as const' instead of enums
const Direction = {
  Up: "UP",
  Down: "DOWN",
  Left: "LEFT",
  Right: "RIGHT"
} as const;

// Create a type from the object values
type Direction = typeof Direction[keyof typeof Direction];
// Type is: "UP" | "DOWN" | "LEFT" | "RIGHT"

// Usage
let move: Direction = Direction.Up;  // OK
move = "UP";                          // Also OK
move = "DIAGONAL";                    // Error!

// Another example with status codes
const StatusCode = {
  OK: 200,
  BadRequest: 400,
  NotFound: 404,
  ServerError: 500
} as const;

type StatusCode = typeof StatusCode[keyof typeof StatusCode];
// Type is: 200 | 400 | 404 | 500
Enter fullscreen mode Exit fullscreen mode

Best Practice: For new projects, prefer 'as const' objects over enums. They provide the same benefits with better bundle size and simpler JavaScript output. Use enums only when you need reverse mapping (numeric enums) or when working with legacy codebases.

any, unknown, and never

TypeScript provides special types for handling dynamic values:

// any - opts out of type checking (avoid when possible)
let flexible: any = 4;
flexible = "string";    // OK
flexible = true;        // OK
flexible.anything();    // OK (but risky!)

// unknown - type-safe alternative to any
let uncertain: unknown = 4;
uncertain = "string";   // OK

// Must check type before using
if (typeof uncertain === "string") {
  console.log(uncertain.toUpperCase());  // OK
}

// never - represents values that never occur
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}
Enter fullscreen mode Exit fullscreen mode

Tip: Prefer 'unknown' over 'any' when you don't know the type. It forces type checking before use, making your code safer.

void Type

The void type represents the absence of a return value:

// Function that doesn't return anything
function logMessage(message: string): void {
  console.log(message);
}

// Arrow function with void return
const printNumber = (num: number): void => {
  console.log(num);
};
Enter fullscreen mode Exit fullscreen mode

Type Assertions

Type assertions tell TypeScript you know more about a value's type:

// Two syntax options
let someValue: unknown = "Hello, TypeScript!";

// Angle-bracket syntax
let strLength1: number = (<string>someValue).length;

// "as" syntax (required in React - JSX)
let strLength2: number = (someValue as string).length;

// Working with DOM elements
const input = document.getElementById("myInput") as HTMLInputElement;
input.value = "Hello!";

// Non-null assertion
function getValue(arr: number[], index: number): number {
  return arr[index]!;  // Assert it won't be undefined
}
Enter fullscreen mode Exit fullscreen mode

The ! operator is called the non-null assertion operator. It tells TypeScript that you are certain the value will not be null or undefined, even though TypeScript thinks it might be. In the example above, arr[index] could potentially be undefined if the index is out of bounds, but using ! tells TypeScript "trust me, this value exists."

// More examples of non-null assertion (!)
const button = document.getElementById("submit")!;
// Without !, TypeScript thinks button could be null

// Use when you're certain a value exists
interface User {
  name: string;
  email?: string;  // optional property
}

function sendEmail(user: User) {
  // We checked elsewhere that email exists
  const email = user.email!;  // Assert it's not undefined
  console.log("Sending to:", email);
}
Enter fullscreen mode Exit fullscreen mode

Warning: Use the non-null assertion (!) sparingly. It bypasses TypeScript's null checks, so incorrect usage can lead to runtime errors. Prefer proper null checks (if statements or optional chaining ?.) when possible.

Literal Types

Literal types specify exact values a variable can hold:

// String literal types
type Direction = "north" | "south" | "east" | "west";
let heading: Direction = "north";  // OK
heading = "northeast";             // Error!

// Numeric literal types
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 4;  // OK
roll = 7;                // Error!

// In function parameters
function move(direction: "up" | "down" | "left" | "right") {
  console.log(`Moving ${direction}`);
}

move("up");        // OK
move("diagonal");  // Error!
Enter fullscreen mode Exit fullscreen mode

Functions in TypeScript

Function Type Annotations

TypeScript allows type annotations on function parameters and return values:

// Basic function with types
function add(a: number, b: number): number {
  return a + b;
}

// Function type as a variable
let multiply: (x: number, y: number) => number;
multiply = function(x, y) {
  return x * y;
};

// Type alias for function types
type MathOperation = (a: number, b: number) => number;

const subtract: MathOperation = (a, b) => a - b;
const divide: MathOperation = (a, b) => a / b;

// Function with object parameter
function printUser(user: { name: string; age: number }): void {
  console.log(`${user.name} is ${user.age} years old`);
}
Enter fullscreen mode Exit fullscreen mode

What Happens When You Pass Wrong Types?

TypeScript catches type mismatches at compile time, preventing runtime errors before your code even runs:

// Calling functions with wrong types
add(5, 10);       // OK: returns 15
add("5", 10);     // Error: Argument of type 'string' is not
                  // assignable to parameter of type 'number'

multiply(3, 4);   // OK: returns 12
multiply(3, "4"); // Error: Argument of type 'string' is not
                  // assignable to parameter of type 'number'

subtract(10, 5);  // OK: returns 5
subtract(10);     // Error: Expected 2 arguments, but got 1

printUser({ name: "Alice", age: 30 });  // OK
printUser({ name: "Bob" });             // Error: Property 'age' is
                                        // missing in type '{ name: string; }'
printUser("Alice");                     // Error: Argument of type 'string'
                                        // is not assignable to parameter
Enter fullscreen mode Exit fullscreen mode

Key Benefit: These errors appear in your IDE as you type and during compilation - not at runtime. This is one of TypeScript's biggest advantages: catching bugs before your code runs!

Optional & Default Parameters

Functions can have optional (?) and default parameter values:

// Optional parameters (must come after required)
function greet(name: string, greeting?: string): string {
  if (greeting) {
    return `${greeting}, ${name}!`;
  }
  return `Hello, ${name}!`;
}

greet("Alice");         // "Hello, Alice!"
greet("Bob", "Hi");     // "Hi, Bob!"

// Default parameters
function createUser(
  name: string,
  role: string = "user",
  active: boolean = true
) {
  return { name, role, active };
}

createUser("Alice");                 // default role & active
createUser("Bob", "admin");          // default active
createUser("Charlie", "mod", false); // all specified
Enter fullscreen mode Exit fullscreen mode

Important: In TypeScript, every function parameter must have its type explicitly defined. Unlike JavaScript, you cannot leave parameters untyped. This requirement ensures type safety throughout your codebase and enables the compiler to catch type-related errors early in development.

// Every parameter needs a type annotation
function process(name: string, count: number, active: boolean) {
  // All parameters have explicit types
}

// This would cause an error in TypeScript:
// function process(name, count, active) { }
// Error: Parameter 'name' implicitly has an 'any' type

// Even callback parameters need types
function fetchData(callback: (data: string) => void) {
  callback("result");
}
Enter fullscreen mode Exit fullscreen mode

Rest Parameters

Rest parameters accept any number of arguments as an array:

// Rest parameters with type annotation
function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

sum(1, 2);           // 3
sum(1, 2, 3, 4, 5);  // 15

// Rest with other parameters
function buildName(first: string, ...rest: string[]): string {
  return first + " " + rest.join(" ");
}

buildName("John", "Paul", "Smith");  // "John Paul Smith"

// Spread in function calls
const nums: number[] = [1, 2, 3];
sum(...nums);  // 6
Enter fullscreen mode Exit fullscreen mode

Understanding Spread in Function Calls

The spread operator (...) allows you to expand an array into individual arguments when calling a function. This is the opposite of rest parameters - while rest parameters collect multiple arguments into an array, spread expands an array into separate arguments.

// Without spread - you'd have to pass each element manually
const numbers = [10, 20, 30];
sum(numbers[0], numbers[1], numbers[2]);  // 60 - tedious!

// With spread - the array is expanded into individual arguments
sum(...numbers);  // 60 - much cleaner!

// How it works:
// sum(...numbers) is equivalent to sum(10, 20, 30)

// Combining arrays with spread
const moreNumbers = [40, 50];
sum(...numbers, ...moreNumbers);  // 150

// Spread with Math functions
const values = [5, 10, 3, 8, 1];
Math.max(...values);  // 10
Math.min(...values);  // 1

// TypeScript ensures type safety with spread
const strings = ["a", "b", "c"];
sum(...strings);  // Error: Argument of type 'string' is not
                  // assignable to parameter of type 'number'
Enter fullscreen mode Exit fullscreen mode

Tip: The spread operator is especially useful when working with arrays of unknown length, or when you want to pass array elements as individual function arguments without modifying the original function.

Function Overloading

Function overloading lets you define multiple signatures for one function:

// Overload signatures
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;

// Implementation
function format(value: string | number | Date): string {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else if (typeof value === "number") {
    return value.toFixed(2);
  } else {
    return value.toISOString();
  }
}

format("hello");      // "HELLO"
format(3.14159);      // "3.14"
format(new Date());   // "2024-01-15T...
Enter fullscreen mode Exit fullscreen mode

Want to Learn More?

This article covers just the fundamentals of TypeScript from the TypeScript + React ebook. To master advanced topics like Generics, Advanced Types, Utility Types, and TypeScript with React Integration, check out the complete ebook:

📘 Beginner's Guide To TypeScript + React Integration - From TypeScript Basics to React Integration

More Such Useful Ebooks Are On The Way In Upcoming Weeks🔥


About Me

I'm a freelancer, mentor, full-stack developer working primarily with React, Next.js, and Node.js with a total of 12+ years of experience.

Alongside building real-world web applications, I'm also an Industry/Corporate Trainer training developers and teams in modern JavaScript, Next.js and MERN stack technologies, focusing on practical, production-ready skills.

Also, created various courses with 3000+ students enrolled in these courses.

My Portfolio: https://yogeshchavan.dev/

Top comments (0)