1
Types & Variables
Declaring what kind of data you have
stringnumberbooleantype inferencearrays
// Explicit types
let name: string = "Bella"
let age: number = 8
let available: boolean = true
// Type inference — TypeScript figures it out
let breed = "French Bulldog" // inferred: string
let price = 1200 // inferred: number
// Arrays
let names: string[] = ["Bella", "Max"]
let prices: number[] = [800, 1200, 950]
// Union type — value must be one of these
type Status = "available" | "reserved" | "sold"
let status: Status = "available"
status = "lost" // ❌ Error! Not in the union
💡Unlike Java, TypeScript has one 'number' type for everything — int, float, double. The colon syntax declares the type.
2
Interfaces
Defining the shape of your objects
interfaceoptional ?readonlyOmit<>Record<>
interface Todo {
id: string // required
text: string // required
completed: boolean // required
priority: Priority // required
createdAt: string // required
}
interface Puppy {
readonly id: number // can't change after creation
name: string
description?: string // ? = optional
}
// Omit removes fields from an existing interface
type NewTodo = Omit<Todo, "id" | "createdAt">
// NewTodo is: { text, completed, priority }
// Record<K, V> = typed object / map
const colors: Record<Priority, string> = {
low: "green",
medium: "yellow",
high: "red",
}💡interface is like a Java POJO / model class — but lighter. No constructor, no getters/setters. TypeScript checks the shape at compile time.
3
Functions
Typing inputs, outputs, and event handlers
return typesvoidarrow functionsReact events
// Named function
function formatPrice(price: number): string {
return price + " €"
}
// Arrow function — very common in React
const double = (n: number): number => n * 2
// void = returns nothing
const logMsg = (msg: string): void => {
console.log(msg)
}
// React event types
const handleSubmit = (
e: React.FormEvent<HTMLFormElement>
): void => {
e.preventDefault()
// ...
}
const handleChange = (
e: React.ChangeEvent<HTMLInputElement>
): void => {
setValue(e.target.value)
}💡The syntax after the closing ) is the return type. void means the function returns nothing — like Java void methods.
4
useState with Types
Typed React hooks
useState<T>type narrowinguseMemogenerics
import { useState, useMemo } from "react"
// Typed state hooks
const [todos, setTodos] = useState<Todo[]>([])
const [input, setInput] = useState<string>("")
const [filter, setFilter] = useState<FilterType>("all")
// Adding to state — 'prev' is automatically typed as Todo[]
const addTodo = (text: string): void => {
setTodos((prev: Todo[]) => [
{ id: crypto.randomUUID(), text, completed: false },
...prev,
])
}
// useMemo — typed return value is inferred
const filtered = useMemo(
() => todos.filter((t) => !t.completed),
[todos] // only recalculates when todos changes
)💡useState<Todo[]> tells TypeScript the state is an array of Todo. Without it, TypeScript infers 'never[]' which is useless.
5
Next.js App Router
Server vs Client components
use clientServer Componentpage.tsxlayout.tsxLink
// app/page.tsx — SERVER COMPONENT (default)
// Runs on the server. No useState allowed here.
// Good for: static content, data fetching, SEO
import Link from "next/link"
export default function HomePage() {
return (
<main>
<h1>Hello from the server!</h1>
<Link href="/todo">Go to Todo App</Link>
</main>
)
}
// ─────────────────────────────────────────────
// app/todo/page.tsx — CLIENT COMPONENT
// "use client" at the top — runs in the browser
// Can use: useState, useEffect, event handlers
"use client"
import { useState } from "react"
export default function TodoPage() {
const [todos, setTodos] = useState<Todo[]>([])
// ...
}💡This is what makes Next.js different from plain React. By default, every file is a Server Component. Add 'use client' only when you need state or events.
Ready to build?
All 5 concepts are used in the Todo app. Open it and study the code.
Open the Todo App →