Adding TypeScript to the React Project
Adding TypeScript to the Project
~14 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
From here on, we'll gradually migrate produktkatalog-web to TypeScript. IMPORTANT: this and the next three chapters show HOW to combine TypeScript WITH React – typing props, typing hooks, typing events. The TypeScript LANGUAGE itself (every feature from the ground up, interface vs. type, generics, utility types, ...) is covered very thoroughly in its own, separate TypeScript tutorial – here we assume basic TypeScript knowledge and focus on the React INTEGRATION.
Why TypeScript is worth it for a React project
Remember bugs like "product.price.toFixed is not a function", because price unexpectedly turned out to be a string instead of a number? Or a typo in a prop name (onAddToCard instead of onAddToCart) that only surfaced while testing in the browser? TypeScript catches both classes of errors WHILE you're typing in the editor, BEFORE the first run.
Switching the Vite project to TypeScript
Vite supports TypeScript natively – no separate build pipeline needed, just the right packages and file extensions. Install the TypeScript type definitions for React:
npm install --save-dev typescript @types/react @types/react-domCreating tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}"noEmit": true– TypeScript is used HERE only for type-checking, not for compiling; Vite/esbuild handle the actual compilation (simply stripping the types in the process)."jsx": "react-jsx"– enables the modern JSX transform, which no longer requiresimport React from 'react'in every file (identical to Vite's JavaScript default)."strict": true– enables ALL strict type checks at once (includingstrictNullChecks, without whichnull/undefinedwould be allowed almost everywhere) – ALWAYS recommended for a new project; enabling it retroactively on a large existing codebase is much more painful.
Converting the first file: cartSlice.js
TypeScript files with JSX syntax get the .tsx extension, plain logic files without JSX get .ts. Let's start with a file WITHOUT JSX – src/store/cartSlice.js becomes cartSlice.ts:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface CartItem {
sku: string;
name: string;
price: number;
quantity: number;
}
interface CartState {
items: CartItem[];
}
const initialState: CartState = { items: [] };
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addItem(state, action: PayloadAction>) {
const { sku, name, price } = action.payload;
const existing = state.items.find((item) => item.sku === sku);
if (existing) {
existing.quantity += 1;
} else {
state.items.push({ sku, name, price, quantity: 1 });
}
},
removeItem(state, action: PayloadAction<string>) {
state.items = state.items.filter((item) => item.sku !== action.payload);
},
updateQuantity(state, action: PayloadAction<{ sku: string; quantity: number }>) {
const { sku, quantity } = action.payload;
const item = state.items.find((item) => item.sku === sku);
if (item) {
item.quantity = Math.max(1, quantity);
}
},
clearCart(state) {
state.items = [];
},
},
});
export const { addItem, removeItem, updateQuantity, clearCart } = cartSlice.actions;
export default cartSlice.reducer; export interface CartItem is the most important new building block: a reusable type definition we'll import in SEVERAL files over the coming chapters (ProductCard, CartPage, ...) – ONE single place describing "what a cart item is". PayloadAction<T> is Redux Toolkit's own generic type for actions – T describes the shape of action.payload. Omit<CartItem, 'quantity'> takes CartItem and REMOVES the quantity field – exactly matching our addItem call, which never passes a quantity (that's always set to 1 or incremented internally).
src/store/index.js to index.ts
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './cartSlice';
export const store = configureStore({
reducer: {
cart: cartReducer,
},
});
// Types derived from the store ITSELF - never need to be kept in sync manually,
// since they automatically adapt to changes in the store:
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;ReturnType<typeof store.getState> is a classic TypeScript pattern: instead of maintaining RootState by hand (error-prone, easily gets out of sync), the type is AUTOMATICALLY DERIVED from the actual store configuration. RootState/AppDispatch will be needed in the next chapter for type-safe useSelector/useDispatch calls.
Achtung: From now on, .js/.jsx and .ts/.tsx files can COEXIST in the same project – the migration doesn't have to happen all at once. That's exactly how we'll proceed over the coming chapters: convert file by file, not everything at once.