Modern examples of TypeScript integration with React examples
Quick, real examples of TypeScript integration with React examples
Let’s start with the kind of code you actually see in a modern React + TypeScript project. These examples of TypeScript integration with React examples assume:
- React 18+
- TypeScript 5+
- JSX in
.tsxfiles
// src/components/UserCard.tsx
import React from "react";
interface User {
id: number;
name: string;
email?: string; // optional
}
interface UserCardProps {
user: User;
onSelect: (userId: number) => void;
}
export function UserCard({ user, onSelect }: UserCardProps) {
return (
<article onClick={() => onSelect(user.id)}>
<h2>{user.name}</h2>
{user.email && <p>{user.email}</p>}
</article>
);
}
That’s a simple but very real example of TypeScript integration with React: typed props, optional fields, and a callback with a clear contract. Now let’s expand into patterns you’ll hit as your app grows.
Example of typed React state and events
Early in a migration, teams usually start with typed state and event handlers. The best examples are small but highlight the value immediately.
import React, { useState, ChangeEvent, FormEvent } from "react";
interface LoginFormValues {
email: string;
password: string;
}
export function LoginForm() {
const [values, setValues] = useState<LoginFormValues>({
email: "",
password: "",
});
function handleChange(
event: ChangeEvent<HTMLInputElement>
) {
const { name, value } = event.target;
setValues(prev => ({
...prev,
[name]: value,
}));
}
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
// values is fully typed here
console.log(values.email, values.password);
}
return (
<form onSubmit={handleSubmit}>
<input
name="email"
type="email"
value={values.email}
onChange={handleChange}
/>
<input
name="password"
type="password"
value={values.password}
onChange={handleChange}
/>
<button type="submit">Log in</button>
</form>
);
}
Here, the example of TypeScript integration with React gives you:
- Strong typing on
useStatewithLoginFormValues - Typed
ChangeEventandFormEventso DOM APIs are discoverable in your editor
API fetching: examples include typed data and errors
Most production React apps talk to APIs. These examples of TypeScript integration with React examples show how to type both responses and errors in a way that works nicely with fetch or libraries like React Query.
// src/api/types.ts
export interface Todo {
id: number;
title: string;
completed: boolean;
}
export interface ApiError {
status: number;
message: string;
}
// src/api/client.ts
export async function fetchTodos(): Promise<Todo[]> {
const res = await fetch("/api/todos");
if (!res.ok) {
const err: ApiError = {
status: res.status,
message: res.statusText,
};
throw err;
}
const data: Todo[] = await res.json();
return data;
}
Then in a component:
import React, { useEffect, useState } from "react";
import { fetchTodos, Todo, ApiError } from "../api";
export function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
const [error, setError] = useState<ApiError | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
let mounted = true;
setLoading(true);
fetchTodos()
.then(data => {
if (mounted) setTodos(data);
})
.catch((err: ApiError) => {
if (mounted) setError(err);
})
.finally(() => {
if (mounted) setLoading(false);
});
return () => {
mounted = false;
};
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<label>
<input type="checkbox" checked={todo.completed} readOnly />
{todo.title}
</label>
</li>
))}
</ul>
);
}
This is one of the best examples of TypeScript integration with React for teams: the types for Todo and ApiError are shared across API clients, components, and tests.
For deeper background on typed JSON handling and API contracts, it’s worth reading up on general API design guidance from organizations like NIST or web standards work via W3C, even though they’re not React‑specific.
Context API: examples of shared state with TypeScript
Shared global state is where TypeScript earns its keep. Here’s an example of a typed auth context, a very common example of TypeScript integration with React examples in production.
// src/auth/AuthContext.tsx
import React, { createContext, useContext, useState, ReactNode } from "react";
interface AuthUser {
id: string;
name: string;
roles: ("admin" | "editor" | "viewer")[];
}
interface AuthContextValue {
user: AuthUser | null;
login: (user: AuthUser) => void;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<AuthUser | null>(null);
function login(nextUser: AuthUser) {
setUser(nextUser);
}
function logout() {
setUser(null);
}
const value: AuthContextValue = { user, login, logout };
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthContextValue {
const ctx = useContext(AuthContext);
if (!ctx) {
throw new Error("useAuth must be used within an AuthProvider");
}
return ctx;
}
This single example of TypeScript integration with React protects you from a ton of runtime bugs:
- Consumers of
useAuthalways see a non‑null context value rolesare constrained to a literal union, which improves autocomplete and prevents typos
useReducer and discriminated unions: best examples for complex state
When state transitions get complicated, useReducer with TypeScript discriminated unions is one of the best examples of a pattern that scales.
import React, { useReducer } from "react";
interface CounterState {
count: number;
step: number;
}
type CounterAction =
| { type: "increment" }
| { type: "decrement" }
| { type: "setStep"; payload: number };
function counterReducer(state: CounterState, action: CounterAction): CounterState {
switch (action.type) {
case "increment":
return { ...state, count: state.count + state.step };
case "decrement":
return { ...state, count: state.count - state.step };
case "setStep":
return { ...state, step: action.payload };
default:
// Exhaustiveness check
const _never: never = action;
return state;
}
}
export function Counter() {
const [state, dispatch] = useReducer(counterReducer, {
count: 0,
step: 1,
});
return (
<section>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<input
type="number"
value={state.step}
onChange={e => dispatch({ type: "setStep", payload: Number(e.target.value) })}
/>
</section>
);
}
This example of TypeScript integration with React gives you:
- Type‑safe dispatch calls
- Exhaustive
switchchecking, so you can’t forget a case when you add a new action type
Hooks: examples include generic custom hooks with TypeScript
Custom hooks are where TypeScript can feel intimidating, so it helps to see real examples. Here’s a generic useLocalStorage hook.
import { useEffect, useState } from "react";
export function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
if (typeof window === "undefined") return initialValue;
try {
const item = window.localStorage.getItem(key);
return item ? (JSON.parse(item) as T) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
// ignore quota errors, etc.
}
}, [key, value]);
return [value, setValue] as const;
}
Usage:
const [theme, setTheme] = useLocalStorage<"light" | "dark">("theme", "light");
This is one of those examples of TypeScript integration with React examples that shows off generics in a way that’s practical, not academic.
React Router and navigation: example of typed route params
Routing is another area where teams want type safety. With React Router v6, you can add types for route params and loader data.
// src/routes/UserDetail.tsx
import React from "react";
import { useParams } from "react-router-dom";
interface UserRouteParams {
userId: string; // from the URL
}
export function UserDetail() {
const { userId } = useParams<UserRouteParams>();
return <h1>User {userId}</h1>;
}
In a larger app, you might centralize route definitions and infer types from them. That’s a more advanced example of TypeScript integration with React, but the idea is the same: let the types describe your URL structure so navigation bugs are caught at compile time.
For general guidance on building reliable web navigation and accessibility patterns, the WAI-ARIA docs at W3C are a solid reference.
React Query / data fetching libraries: real examples from 2024–2025
React Query (now TanStack Query) is everywhere in 2024–2025. Here’s how examples of TypeScript integration with React examples look when you add it to the mix.
// src/api/users.ts
export interface UserSummary {
id: number;
name: string;
}
export async function fetchUsers(): Promise<UserSummary[]> {
const res = await fetch("/api/users");
if (!res.ok) throw new Error("Failed to fetch users");
return res.json();
}
// src/components/UserList.tsx
import React from "react";
import { useQuery } from "@tanstack/react-query";
import { fetchUsers, UserSummary } from "../api/users";
export function UserList() {
const { data, isLoading, error } = useQuery<UserSummary[]>({
queryKey: ["users"],
queryFn: fetchUsers,
});
if (isLoading) return <p>Loading users...</p>;
if (error instanceof Error) return <p>Error: {error.message}</p>;
return (
<ul>
{data?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
This real example of TypeScript integration with React shows how generics on useQuery keep data strongly typed throughout your component tree.
useRef and DOM elements: small examples that prevent big bugs
useRef is another place where adding types pays off, especially as your UI logic gets more interactive.
import React, { useRef, useEffect } from "react";
export function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;
}
This tiny example of TypeScript integration with React makes the DOM API discoverable and prevents you from calling methods that don’t exist on that element type.
2024–2025 trends in TypeScript + React
A few trends are shaping how new projects approach examples of TypeScript integration with React examples:
- Stricter configs by default: New React + TS templates often enable
strict,noImplicitAny, andstrictNullChecks. That means your examples need to be fully typed, not halfway. - More server‑side React: With frameworks like Next.js and Remix, TypeScript types now span server components, loaders, and client components. Sharing types across server and client is becoming standard.
- Stronger focus on accessibility and reliability: As apps handle more health, finance, and education data, teams care more about catching bugs early. While not React‑specific, resources like Harvard’s digital accessibility guidance can inform how you structure typed components and props.
All of that makes real, modern examples of TypeScript integration with React examples more valuable than ever. The patterns above are the ones that keep showing up in serious apps: typed API clients, context, reducers, hooks, and routing.
FAQ: common questions about examples of TypeScript integration with React
What is a simple example of adding TypeScript to an existing React component?
Rename the file from .jsx to .tsx, add an interface for props, and annotate your function. For example:
interface ButtonProps {
label: string;
onClick?: () => void;
}
export function Button({ label, onClick }: ButtonProps) {
return <button onClick={onClick}>{label}</button>;
}
That’s often the first example of TypeScript integration with React that teams adopt because it’s low‑risk and immediately helpful.
Do I need to type everything in my React app right away?
No. Many teams start with props and API responses, then move to context, reducers, and custom hooks. The best examples of migration are incremental: keep JavaScript where it’s still changing rapidly, and add TypeScript where contracts are relatively stable.
Are there examples of TypeScript integration with React in large open‑source projects?
Yes. Many popular libraries and frameworks maintain React + TypeScript examples, including Next.js and Remix starter templates. Browsing those repositories is a good way to see real examples of folder structure, tsconfig settings, and shared types.
How strict should my TypeScript config be for a React project?
For new projects, most teams now aim for "strict": true in tsconfig.json. That matches how modern examples of TypeScript integration with React examples are written in documentation and tutorials. For legacy apps, you can enable strictness gradually and fix errors by module or feature.
If you keep these patterns in your toolbox—typed props, state, context, reducers, hooks, routing, and data fetching—you’ll have a solid set of real‑world examples of TypeScript integration with React to apply across nearly any project.
Related Topics
Modern examples of diverse examples of TypeScript decorators
Modern examples of diverse examples of TypeScript generics
Modern examples of TypeScript integration with Node.js examples
Practical examples of TypeScript configuration examples for modern projects
Modern examples of TypeScript integration with React examples
Explore More TypeScript Code Snippets
Discover more examples and insights in this category.
View All TypeScript Code Snippets