Skip to main content

React TypeScript Basic Types Tutorial: Primitives & Annotations

Every variable in TypeScript has a type—a contract that says "this value is always a number" or "this value is always a string". Unlike JavaScript, TypeScript checks these contracts at compile time. Once you understand TypeScript's primitive types and how to annotate them, you'll be able to write React code that refuses to compile if you make a type mistake, catching bugs before the browser even loads.

What Are TypeScript Primitive Types?

Primitive types are the foundational building blocks of TypeScript: the types that represent single values like numbers, strings, and booleans. TypeScript offers six primitives: string, number, boolean, null, undefined, symbol, and bigint. Understanding each one is the gateway to writing typed React code.

Here is a reference table of TypeScript primitives and their typical use:

TypeValuesExampleReact Use
stringText"Hello", 'World'Input values, labels, messages
numberIntegers, decimals42, 3.14, InfinityCounts, ages, dimensions, IDs
booleanTrue/falsetrue, falseFlags, conditions, toggle states
nullNull valuenullExplicitly no value (rare in React)
undefinedUndefined valueundefinedMissing props, uninitialized state
anyAny type (escape hatch)Any valueAvoid; disables type checking
neverNo value possibleFunction that throwsExhaustiveness checks, type narrowing

How Do You Annotate Types in TypeScript?

Type annotation is the syntax for declaring a variable's type explicitly. You use a colon (:) followed by the type name. Here are the most common patterns in React development:

// String annotation
const greeting: string = "Hello, TypeScript!";

// Number annotation
const count: number = 42;

// Boolean annotation
const isActive: boolean = true;

// Array annotations
const names: string[] = ["Alice", "Bob", "Charlie"];
const numbers: Array<number> = [1, 2, 3];

// Union type (multiple options)
const id: string | number = 123; // can be string OR number

// Explicit null/undefined
const nullable: string | null = null;
const optional: string | undefined = undefined;

The syntax is straightforward: const <name>: <type> = <value>. TypeScript then checks that the value matches the declared type. If you assign the wrong type, the compiler rejects it:

// ERROR: Type 'string' is not assignable to type 'number'
const count: number = "forty-two";

What Is Type Inference and When Can You Omit Type Annotations?

Type inference is TypeScript's ability to figure out a type automatically from the value you assign. When you write const name = "Alice", TypeScript infers that name has type string without you writing : string. This is why many developers omit simple annotations for variables—TypeScript is smart enough to deduce the type.

Inference works well for straightforward assignments:

// Inferred as string
const message = "Welcome to TypeScript";

// Inferred as number
const year = 2026;

// Inferred as boolean
const isDarkMode = false;

// Inferred as string[] (array of strings)
const colors = ["red", "blue", "green"];

However, you should explicitly annotate when the intent is not obvious or when you want to be defensive about the type. For example, function parameters almost always need annotations because the function author (you) should declare what types you accept:

function calculateDiscount(price: number, percent: number): number {
return price * (1 - percent / 100);
}

const discountedPrice = calculateDiscount(100, 10); // Returns 90

Here, the annotations on price and percent are mandatory—without them, TypeScript would infer unknown and reject the arithmetic operations inside the function body. The : number at the end is the return type, telling callers that this function always returns a number.

How Do You Use Union Types in React?

A union type allows a variable to be one of several types. You combine types with the pipe (|) operator. Union types are extremely common in React for describing props that can accept multiple value types, or state that might be in several states:

// A status that can be one of three strings
type Status = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<Status>("idle");

// A value that can be a string or a number (like an ID)
let identifier: string | number = 42;
identifier = "user-123"; // Also valid

// Optional prop pattern
function Button({ label }: { label: string | null }) {
return <button>{label || "Unnamed"}</button>;
}

Union types force you to handle all cases. If you have a Status variable and you write a conditional, TypeScript ensures you handle every possible value:

const displayStatus = (status: Status) => {
if (status === "idle") {
return "Ready...";
} else if (status === "loading") {
return "Fetching...";
} else if (status === "success") {
return "Done!";
} else if (status === "error") {
return "Oops!";
}
// TypeScript knows all cases are covered
};

What Is the Difference Between null and undefined?

Both represent "no value," but they mean different things in React code. null is an explicit absence of value (you set it intentionally), while undefined means a value was never assigned. In practice, React code uses undefined far more often.

Understanding the distinction helps you write defensive code:

// undefined: prop was not passed
function Card({ title }: { title?: string }) {
// title is string | undefined
return <div>{title || "Untitled"}</div>;
}

// null: value was explicitly set to no value
function Profile({ avatar }: { avatar: string | null }) {
// avatar is string | null
return <img src={avatar || "/default-avatar.png"} />;
}

const result: number | null = null; // Explicit empty value
const notYetAssigned: number | undefined = undefined; // Not assigned

Many React hooks return undefined for missing state (e.g., useState<string> where state hasn't been initialized), while API responses might use null to indicate a missing field. You can use both in React, but be consistent within your codebase.

How Do You Work with Arrays and Tuples?

Arrays in TypeScript are typed by their element type. You specify the element type with either Type[] or Array<Type> syntax. Tuples are fixed-length arrays where each position has a specific type, useful for describing the shape of destructured values or return values from functions.

// Array of strings
const userNames: string[] = ["Alice", "Bob"];

// Array of numbers
const scores: number[] = [95, 87, 92];

// Array of mixed types (union)
const mixed: (string | number)[] = ["user-1", 42, "user-2"];

// Tuple: fixed length, specific types at each position
const response: [number, string] = [200, "OK"];
const [statusCode, message] = response; // statusCode is number, message is string

// Tuple with optional elements
const optional: [string, number?] = ["name"];

Arrays and tuples are common in React for mapping over lists of items or returning multiple values from a hook:

// Array used in render
const TodoList = ({ todos }: { todos: string[] }) => (
<ul>
{todos.map((todo) => <li key={todo}>{todo}</li>)}
</ul>
);

// Tuple for hook return
const useToggle = (initial: boolean): [boolean, () => void] => {
const [state, setState] = useState(initial);
const toggle = () => setState(!state);
return [state, toggle];
};

Key Takeaways

  • TypeScript's six primitive types are string, number, boolean, null, undefined, and symbol. Use them to annotate variables and function parameters.
  • Type annotations use the colon syntax: const name: string = "Alice".
  • Type inference automatically deduces types from values; omit annotations for simple assignments but always annotate function parameters.
  • Union types (string | number) let variables be one of several types; you must handle all cases in conditionals.
  • null and undefined mean different things; use undefined for optional values and null for explicit empty values.
  • Arrays are typed by element (string[]); tuples fix length and position types ([number, string]).

Frequently Asked Questions

What is the any type and should I use it?

The any type disables type checking—TypeScript treats any as any value, so you can assign it to anything without errors. It is an escape hatch for legacy code or truly unknown types. Avoid it in new code; it defeats TypeScript's purpose. Use unknown if you truly don't know the type and need to check it at runtime.

Do I have to annotate every variable?

No. TypeScript infers types from assignments, so const count = 42 works without annotation. Annotate function parameters and return types; annotate variables when the intent is unclear or when you want to enforce a stricter type than inference would give you.

Can I use TypeScript without JSX files?

Yes. Type .ts files are pure TypeScript; .tsx files mix TypeScript and JSX. Most React projects use .tsx for component files (which have JSX) and .ts for utility functions (which don't).

How do I type a function that returns nothing?

Use the : void return type annotation. void means "no return value" or "the return value is ignored":

const logMessage = (msg: string): void => {
console.log(msg);
};

What is strictNullChecks and why does it matter?

strictNullChecks (enabled by strict: true in tsconfig) forces you to explicitly handle null and undefined. Without it, TypeScript treats null and undefined as assignable to any type, hiding bugs. Always enable it.

Further Reading