// Typed useState
const [user, setUser] =
useState<User | null>(null);
// Typed useReducer
type EditorAction =
| { type: 'SET_IMAGE';
payload: Image }
| { type: 'APPLY_FILTER';
payload: Filter }
| { type: 'UNDO' }
| { type: 'REDO' }
| { type: 'SET_ZOOM';
payload: number };
function editorReducer(
state: EditorState,
action: EditorAction
): EditorState {
switch (action.type) {
case 'SET_IMAGE':
return { ...state,
currentImage: action.payload };
case 'APPLY_FILTER':
return { ...state,
activeFilter: action.payload,
isDirty: true };
case 'UNDO':
return { ...state,
historyIndex:
state.historyIndex - 1 };
// TS enforces: every case handled
}
}
// Custom hook with typed return
function useEditor() {
const [state, dispatch] =
useReducer(editorReducer, initialState);
const applyFilter =
(filter: Filter): void => {
dispatch({
type: 'APPLY_FILTER',
payload: filter });
};
return { state, applyFilter }
as const;
// as const = readonly tuple
}
// contexts/AuthContext.tsx
interface AuthContextType {
user: User | null;
token: string | null;
login: (email: string,
password: string) => Promise<void>;
logout: () => void;
isAuthenticated: boolean;
}
const AuthContext =
createContext<AuthContextType | null>(
null);
// Typed hook with null guard
function useAuth(): AuthContextType {
const context =
useContext(AuthContext);
if (!context) {
throw new Error(
'useAuth must be used within '
+ 'AuthProvider');
}
return context;
}
// Now in any component:
const { user, login, logout }
= useAuth();
// user is User | null
// login is fully typed
// TS catches: user.name
// (might be null — handle it!)
// api/client.ts
interface ApiResponse<T> {
data: T;
message?: string;
}
interface ApiError {
error: string;
details?: Array<{
field: string;
message: string;
}>;
requestId: string;
}
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: ApiError };
async function apiGet<T>(
path: string
): Promise<Result<T>> {
try {
const res = await fetch(
`/api/v1${path}`, {
headers: {
Authorization:
`Bearer ${getToken()}`,
},
});
if (!res.ok) {
const error: ApiError =
await res.json();
return { ok: false, error };
}
const data: T = await res.json();
return { ok: true, data };
} catch (e) {
return { ok: false, error: {
error: 'Network error',
requestId: 'local',
}};
}
}
// Usage — TS knows the shape:
const result =
await apiGet<Image[]>('/images');
if (result.ok) {
// result.data is Image[]
result.data.map(img => img.title);
} else {
// result.error is ApiError
console.error(result.error.error);
}
// server/routes/images.ts
import { Request, Response } from 'express';
interface AuthRequest extends Request {
userId: string;
}
interface PaginationQuery {
page: number;
limit: number;
sort: 'createdAt' | 'name' | 'size';
}
type CreateImageBody =
Pick<Image, 'title'>
& Partial<Pick<Image,
'description' | 'tags'>>;
router.get('/images',
authenticate,
async (
req: AuthRequest,
res: Response
) => {
const { page, limit } =
req.query as PaginationQuery;
const images =
await Image.find({
owner: req.userId })
.skip((page - 1) * limit)
.limit(limit);
const response: ApiResponse<Image[]>
= { data: images };
res.json(response);
});
// tsconfig.json — the final step
{
"compilerOptions": {
"strict": true,
// Enables ALL of these:
// strictNullChecks
// noImplicitAny
// strictFunctionTypes
// strictBindCallApply
// strictPropertyInitialization
// noImplicitThis
// alwaysStrict
"allowJs": false,
// ↑ No more JS files allowed
"noUncheckedIndexedAccess": true,
// ↑ array[0] might be undefined
}
}
// Run: npx tsc --noEmit
// Fix every error.
// When it compiles clean: you're done.
git switch -c feature/PIXELCRAFT-076-typescript-migration
git add src/ server/ types/ tsconfig.json
git commit -m "Complete TypeScript migration + strict mode (PIXELCRAFT-076)"
git push origin feature/PIXELCRAFT-076-typescript-migration
# PR → Review → Merge → Close ticket ✅
TypeScript's type system is Turing-complete.
The type system is so powerful it can compute at compile time. You can write type-level programs that validate data shapes, parse strings, and enforce constraints — all before a single line of JavaScript runs.
npx tsc --noEmit → 0 errors. Every file is TypeScript. Strict mode is on. Every component, every hook, every API call is fully typed. Autocomplete works everywhere. Entire classes of bugs are now impossible.