Skip to content
React Course1.0.0 beta
CSEN
React Course1.0.0 beta
CSEN
🏠 Home🧭 Where to start?🔁 Review📄 Cheat sheets📰 What's newℹ️ About the course
JavaScript0/22
TypeScript0/6

TypeScript basics

  • 01 Where the type goes (TypeScript syntax)0/3
  • 02 Types that change (unions and narrowing)0/3

Basics

  • 01 A quick review of the basics0/3
  • 02 State and events (useState)0/3

Hooks in depth

  • 03 Effects (useEffect)0/3
  • 04 Refs (useRef)0/3
  • 05 More complex state (useReducer)0/3
  • 06 Context0/3
  • 07 Custom hooks0/3

Forms

  • 08 Forms and React 19 Actions0/3

Performance

  • 09 Performance and rendering0/3

Patterns

  • 10 Component design patterns0/3

Ecosystem

  • 11 TypeScript with React0/3
  • 12 Routing (React Router)0/3
  • 13 Data fetching0/3
  • 14 Application state management0/3

Project

  • 15 Final project: Kanban0/2

Summary

  • 16 Summary: the principles of React

Your account

Privacy

TypeScript 01 · TypeScript basics

Where the type goes (TypeScript syntax)

When a colon, when <…>, when nothing. The anatomy of the syntax, a cheat sheet of situations and a fill-in exercise.

Loading lesson…

💬 Found a mistake, something unclear, or have an idea? Let me know.
← JS patterns you meet everywhere in ReactTypes that change (unions and narrowing) →

Before we start: labels on moving boxes

When you move house, you stick labels on the boxes: "kitchen – glass", "books", "winter clothes". The boxes themselves don't care about the label – the things inside are still the same. But the movers can tell from the labels what mustn't go under heavy books and where to take what. And once everything is unpacked, you peel off the labels and throw them away.

TypeScript is labels on JavaScript. You stick them on variables, parameters and properties: "there's a number here", "there's a user or nothing here". The code doesn't work any differently because of them – but the editor and the compiler can tell from them when you try to put glass under books (pass text where a number should be). And when compiling, all the labels are peeled off: the browser gets clean JavaScript.

This lesson isn't about which labels exist, but where they're stuck: when a colon, when <…> and when nothing. Once you have that down, the types in the lessons that follow won’t catch you out.

See-through and opaque boxes

A see-through box doesn't need a label – you can see what's in it. In the same way, with const count = 0 TypeScript sees the zero and knows by itself it's a number. This is called type inference. You have to stick a label where you can't see inside: on an empty box (an empty array), on a box that's still standing empty for now (null), or on a box that's only on its way (a function parameter, data from an API).

ℹ️ What's in this lesson
  1. One main rule and the anatomy of a line with a type.
  2. Where the colon goes and where (treacherously) it doesn't.
  3. When to write a type and when to let it be inferred.
  4. A cheat sheet for typical situations, a glossary of symbols and a fill-in exercise for practice.

1. One main rule

ℹ️ name: Type

A colon after the name of anything you declare (a variable, a parameter, a property) means "is of type". The order is always the same: first the name, then the colon, then the type.

let price: number // a variable
function f(price: number) // a parameter
{ price: number } // a property in a type

The second thing worth knowing: types disappear after compiling – the labels are peeled off. When you mentally erase all the types from the code, valid JavaScript must remain. That's the best test of whether a type is in the right place.

Let's try it on a typical function:

function formatPrice(amount: number, currency?: string): string {
return amount + ' ' + (currency ?? 'USD')
}
  1. amount: number – the parameter amount is of type number. Name, colon, type.
  2. currency?: string – the question mark belongs to the name: the parameter is optional. Inside the function it can be undefined, hence ?? 'USD'.
  3. ): string – a colon after the parameter brackets is the type of what the function returns. The same rule, only this time the "name" is the whole function call.

Now let's peel the labels off:

function formatPrice(amount, currency) {
return amount + ' ' + (currency ?? 'USD')
}

Valid JavaScript remains. If it didn't – say if ({ title: string }) left behind something that does a different thing – the type was in the wrong place. We'll get to that in the next section.

Demo

The anatomy of a line – click parts of the code

What you see: Several typical lines with types. Each piece is coloured: purple for a type, orange for TypeScript syntax (colons, question marks, brackets), normal for plain JavaScript. Underlined pieces have an explanation.

Try it:

  1. In the Variable example, click price, the colon and number in turn. Under the code you'll see what each piece means.
  2. Switch to Function and find two colons: one at a parameter, the other after the bracket. Click both and compare the explanations.
  3. Go through Component props and useState. For each one, mentally erase the purple and orange parts – valid JavaScript always remains.

The takeaway: A type is always a label stuck on a specific place, most often after a colon. Without it, JavaScript remains that does the same thing.

Loading the interactive part…

2. Where the colon goes and where it doesn't

The colon exists in JavaScript even without TypeScript – in objects it separates the key and the value, in destructuring it renames. That's why it's the most common source of confusion: the same character, three different meanings. It helps to ask: is the colon after something I'm declaring right now? Then it's a label.

PlaceSyntaxNote
A variablelet name: stringOnly when TS doesn’t infer the type from the value.
A parameter(name: string) => …Always.
An optional parameter / propertyname?: stringThe question mark belongs to the name.
A return valuefunction f(): stringAfter ), in an arrow function before =>.
A property in an interface / type{ name: string }Describing the shape of an object.
Component props({ name }: Props)After the whole destructuring!
There is NO colon hereSyntaxWhy
A type definitiontype Id = string | numberYou’re naming a type → an equals sign.
An interface definitioninterface User { … }Keyword + name + body.
A type for a function/hookuseState<User | null>(null)The type is PASSED to the function in angle brackets.
An object with values{ name: 'Anna' }Here the colon separates the key and the VALUE.
JSX<Card title="Hello" />No types in JSX – TS checks them against the props.
Destructuring{ title: heading }Here the colon means RENAMING the variable.
❓ interface or type – when to use which?

For describing the shape of an object (props, data from an API) they work almost the same:

interface User { name: string; age?: number }
type User = { name: string; age?: number } // the same

The difference is in what else they can do. Only type can name anything other than an object: a union (type Status = 'idle' | 'loading'), a tuple, a function type. Only an interface can be extended by another interface of the same name, which is useful mainly in libraries.

A simple rule: interface for object shapes and props, type for everything else. Above all be consistent – nothing else depends on it.

⚠️ The two most common mistakes
// ❌ 1) A type inside destructuring – this is NOT a type, but renaming!
function Card({ title: string }) { … } // creates a variable "string"
// ✅
function Card({ title }: { title: string }) { … }
function Card({ title }: CardProps) { … }
// ❌ 2) Types instead of values in an object
const user = { name: string, age: number }
// ✅ the type separately, the values separately
interface User { name: string; age: number }
const user: User = { name: 'Anna', age: 30 }

3. When to write a type and when not to

Back to the boxes. You stick a label where you can't see inside, and on boxes that arrive from outside. You don't stick one on a see-through box – you'd only add work and the risk that the label won't match the contents.

// See-through boxes – TS sees inside, don't write a label
const count = 0 // number
const names = ['Anna', 'Peter'] // string[]
const [open, setOpen] = useState(false) // boolean
// Boxes arriving from outside – always write a label
function greet(name: string) { … } // a parameter: who knows what someone will send here
interface CardProps { title: string } // props: the same for components
// Opaque boxes – TS can't see inside, add a label
const [user, setUser] = useState<User | null>(null) // null now, a User later
const [todos, setTodos] = useState<Todo[]>([]) // an empty array – of what items?

A simple rule: write types at the boundaries – where data comes in (parameters, props, APIs) or where TS has nothing to work out the type from. Inside functions, let it do its work.

✅ Always write🤔 Write when TS doesn’t infer🚫 Don’t write (unnecessary)
Function parametersuseState<User | null>(null)const x: number = 5
Component props (interface)useState<Todo[]>([])useState<number>(0)
Data shapes (interface User)const data: User = await res.json()Parameters of inline handlers in JSX
Data from an API (JSON)Standalone event handlersCallback parameters (.map(item => …))
Public library functions / utils[object Object] assigned laterThe return type of simple functions
✨ How to find out what TS inferred
Hover over a variable in VS Code. When the tooltip shows exactly what you want, don't write the type. When it shows any, never[] or null, add the type.

4. A cheat sheet: I want to type…

A cheat sheet for the situations you'll meet most often. Use it like a cookbook: find what you're writing right now and look up where the label goes.

Demo

Pick a situation

What you see: Buttons with typical situations, split into plain JavaScript and React. For each situation there's the right syntax, the most common mistake and a short explanation.

Try it:

  1. Click component props. Notice the mistake { title: string } inside the brackets – you'll see it in the quiz in a moment too.
  2. Go through useState. The label is in angle brackets – the type is passed to the hook, not stuck after a colon.
  3. Look at an error in catch and data from an API. These are boxes that arrived from outside and you don't know what's in them.

The takeaway: Most situations repeat. Once you've looked them up in the cheat sheet a few times, you'll start writing labels in the right places automatically.

Loading the interactive part…

5. A glossary of symbols

SymbolExampleMeaning
:age: number"is of type"
?age?: numberoptional (can be missing / undefined)
|string | null"or" – a union
&A & B"and at the same time" – combining types
[]string[]an array
[A, B][string, number]a tuple – an array with a fixed length
=>(id: number) => voida function type
<T>Array<User>a generic – a type as a parameter
asdata as User"trust me" – a cast without a check (use sparingly)
as const['a', 'b'] as constthe most precise (readonly) type possible
!ref.current!"definitely not null" – without a check
typeoftypeof configa type from an existing value
keyofkeyof Usera union of property names: "name" | "age"
❓ What does <T extends { id: number }> mean (the findById challenge)?

<T> on its own means "any type". But then TypeScript wouldn't let you write item.id, because it doesn't know whether T has an id. extends adds a condition: "T can be anything that has at least an id property of type number".

function findById<T extends { id: number }>(items: T[], id: number): T | undefined {
return items.find((item) => item.id === id) // ✅ item.id exists, because T must have it
}
findById(products, 1) // T = Product – returns Product | undefined
findById(users, 1) // T = User
findById(['a', 'b'], 1) // ❌ a string has no id

The result remembers the exact type: from findById(products, 1) you get a Product with all its properties, not just { id: number }. The word extends here doesn't mean inheritance as with classes – read it as "satisfies".

6. How the editor helps you

  • Hovering shows the inferred type of anything.
  • Ctrl + Space in a component's props offers all the allowed props.
  • F12 on a type (say ChangeEvent) jumps to its definition.
  • Ctrl + . (Quick Fix) on a parameter with any offers "Infer parameter types from usage".
  • Read error messages from the bottom. The last line of a long error tends to be the important one ("Property 'email' is missing…").

Practise: a fill-in exercise

Fill in the boxes with what belongs there – a type, a symbol, or nothing. Each exercise trains one place where a label goes. Remember the see-through boxes: sometimes the right answer is to leave the box empty.

Tip: an empty box means "I write nothing here" – that can be the right answer too. Enter = check.Solved 0 / 16
1. A function parameter
function double(n) {
  return n * 2
}
2. A return value
function greet(name: string) {
  return 'Hello ' + name
}
3. An optional parameter
function greet(name: string, excited boolean) {
  return excited ? name + '!' : name
}
4. The shape of an object
interface User {
  name string
  age number     // optional
}
5. A value vs. a type
interface User { name: string }

const anna: User = { name:  }
6. A type as a union
type Status  'idle' | 'loading' | 'error'
7. Component props
interface AvatarProps {
  src: string
  size?: number
}

function Avatar({ src, size = 40 }) {
  return <img src={src} width={size} />
}
8. A callback in props
interface ListProps {
  items: string[]
  onSelect:    // gets the index of the selected item
}
9. useState with null
const [user, setUser] = useState(null)
10. useState with an empty array
const [todos, setTodos] = useState([])
11. When not to write a type
const [count, setCount] = useState(0)
12. A variable filled in later
let selected = null

// later: selected = anna   (anna is a User)
13. A standalone event handler
const handleChange = (e) => {
  setName(e.target.value)
}
14. An async function
async function loadUser(id: number):  {
  const res = await fetch('/api/users/' + id)
  return res.json()
}
15. A generic function
function first(items: T[]): T | undefined {
  return items[0]
}
16. A pair from a function (tuple)
function minMax(nums: number[]) {
  return [Math.min(...nums), Math.max(...nums)] 
}

Where's the mistake?

Quiz What's wrong?
function Badge({ label: string, color: string }) {
return <span style={{ color }}>{label}</span>
}
Quiz Why does setItems([...items, 'new']) report an error?
const [items, setItems] = useState([])
Quiz Which version is correct?
// A
const handleClick: (e: MouseEvent<HTMLButtonElement>) => void = (e) => { … }
// B
const handleClick = (e: MouseEvent<HTMLButtonElement>) => { … }
// C
const handleClick = (e) : MouseEvent<HTMLButtonElement> => { … }
Quiz Where does the type go in an arrow component?
const Card = ({ title }) => <h2>{title}</h2>

Warm-up

Challenge

Types instead of any

Warm-up

Swap any for a real type twice and fill in what the functions return.

Task is in the comments in the code below – at the top of the file and at the places marked TODO.

Loading the interactive part…

Challenges

In both challenges the finished code is full of any – boxes without labels. Your task is to stick the labels in the right places: first in plain functions, then in a React component. Nothing changes on the page; only TypeScript sees the difference.

✨ An automatic check
Both challenges have type tests. Run npm run typecheck:challenges – as long as there's any in your code, you'll see "Unused '@ts-expect-error' directive" errors (it means TS didn't catch an error it should have). When the types are right, the output is empty.
Challenge

Where do the types go? – plain functions

Easy

Seven small functions, seven places where a type goes. The output on the page won't change – only TypeScript sees the difference. Which types go where is in the comments in the code.

Task is in the comments in the code below – at the top of the file and at the places marked TODO.

Loading the interactive part…

Challenge

Where do the types go? – a React component

MediumBonus · free after sign-in

The typical places in a React component: props, a callback, useState and an event handler. Which type goes where is in the comments in the code.

🔓 Bonus challenge – free after sign-in

Medium and hard challenges unlock once you sign in. Like the whole course, they are free – just an e-mail, no password and no payment.

How we handle your data

Summary

  • Types are labels on JavaScript: they don't change the code, they only check it, and they're peeled off when compiling. After erasing them, valid JavaScript must remain.
  • name: Type – a colon after what you declare (a variable, a parameter, a property) means "is of type". The return type goes after the parameter brackets: function f(): T.
  • The props type goes after the whole destructuring: ({ a, b }: Props). Inside { } the colon renames!
  • type X = … (an equals sign), interface X { … }, a type for a hook in angle brackets: useState<T>().
  • Labels on boundaries and opaque boxes (parameters, props, data shapes, null, empty arrays); let TS infer the see-through boxes.
  • Not sure? Hover – the editor will tell you the type.
🧩 Where do the things not defined here come from (products)
products – from the file src/course/fakeApi.en.ts. The course fake API: data and functions pretending to be a server (with a delay, sometimes even with an error). In a real app you would call fetch() here.
export interface Product {
id: number
name: string
category: 'fruit' | 'vegetables' | 'bakery' | 'dairy'
price: number
inStock: boolean
}
export const products: Product[] = [
{ id: 1, name: 'Apple', category: 'fruit', price: 12, inStock: true },
{ id: 2, name: 'Banana', category: 'fruit', price: 8, inStock: true },
{ id: 3, name: 'Pear', category: 'fruit', price: 15, inStock: false },
{ id: 4, name: 'Carrot', category: 'vegetables', price: 6, inStock: true },
{ id: 5, name: 'Tomato', category: 'vegetables', price: 9, inStock: true },
{ id: 6, name: 'Cucumber', category: 'vegetables', price: 19, inStock: false },
{ id: 7, name: 'Bread roll', category: 'bakery', price: 3, inStock: true },
{ id: 8, name: 'Bread', category: 'bakery', price: 45, inStock: true },
{ id: 9, name: 'Milk', category: 'dairy', price: 22, inStock: true },
{ id: 10, name: 'Cheddar', category: 'dairy', price: 39, inStock: true },
{ id: 11, name: 'Yogurt', category: 'dairy', price: 14, inStock: false },
{ id: 12, name: 'Orange', category: 'fruit', price: 11, inStock: true },
]
CSS classes like row, stack, card, list, btn, input or muted are the course's ready-made styles in src/styles.css (row = side by side, stack = stacked, card = bordered box, muted = grey text).