# Typescript Migration

> Migrate React/Node projects from JavaScript to TypeScript — converting PropTypes, adding interfaces, typing hooks, API responses, and event handlers. Step-by-step approach.

- Skill: `kirti/typescript-migration` (Agent Skill)
- Install (CLI): `npx skillmds@latest add kirti/typescript-migration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kirti/typescript-migration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: kirti (https://skillmd.com/u/kirti)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kirti/typescript-migration

---


# TypeScript Migration Skill

## When to Use This Skill
- Converting .js/.jsx files to .ts/.tsx
- Adding types to existing JavaScript code
- Typing React props, state, hooks, and events
- Typing API responses and data models
- Fixing TypeScript errors in existing code

## Migration Strategy
Always migrate incrementally — one file at a time. Never convert the whole project at once.

```
1. Rename file: Component.jsx → Component.tsx
2. Fix immediate errors only
3. Add types gradually — use 'any' temporarily if stuck
4. Remove 'any' types in a second pass
```

---

## Step 1 — Props Interfaces

```tsx
// ❌ JavaScript — no type safety
function UserCard({ name, age, email, onClick, isAdmin }) {
  return <div onClick={onClick}>{name}</div>;
}

// ✅ TypeScript — fully typed
interface UserCardProps {
  name: string;
  age: number;
  email: string;
  onClick: () => void;
  isAdmin?: boolean;          // optional prop
  children?: React.ReactNode; // children is always optional
}

function UserCard({ name, age, email, onClick, isAdmin = false }: UserCardProps) {
  return <div onClick={onClick}>{name}</div>;
}
```

**AI instruction:** Every React component MUST have a Props interface. Optional props use `?`. Children use `React.ReactNode`.

---

## Step 2 — useState with Types

```tsx
// ❌ Inferred as never[] — breaks immediately
const [items, setItems] = useState([]);

// ✅ Explicit generic
const [items, setItems] = useState<Item[]>([]);
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [count, setCount] = useState<number>(0);
```

---

## Step 3 — useRef Types

```tsx
// ❌ Untyped ref
const inputRef = useRef(null);

// ✅ DOM element ref
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);

// ✅ Mutable ref (for values, not DOM)
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const countRef = useRef<number>(0);
```

---

## Step 4 — Event Handlers

```tsx
// ❌ any event
const handleChange = (e: any) => setValue(e.target.value);

// ✅ Specific event types
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setValue(e.target.value);
};

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
};

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  e.stopPropagation();
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
  if (e.key === 'Enter') submit();
};
```

---

## Step 5 — API Response Types

```tsx
// ❌ Untyped — data is any
const fetchUser = async (id: string) => {
  const res = await fetch(`/api/users/${id}`);
  const data = await res.json();
  return data;
};

// ✅ Typed response
interface User {
  id: string;
  name: string;
  email: string;
  createdAt: string;
  role: 'admin' | 'user' | 'viewer';
}

interface ApiResponse<T> {
  data: T;
  error: string | null;
  status: number;
}

const fetchUser = async (id: string): Promise<User> => {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`Failed: ${res.status}`);
  const json: ApiResponse<User> = await res.json();
  return json.data;
};
```

---

## Step 6 — Custom Hooks

```tsx
// ❌ Untyped custom hook
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(initialValue);
  // ...
  return [value, setValue];
}

// ✅ Generic typed hook
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = (value: T) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}
```

---

## Step 7 — Converting PropTypes → TypeScript

```tsx
// PropTypes (before)
UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
  email: PropTypes.string,
  onClick: PropTypes.func.isRequired,
  role: PropTypes.oneOf(['admin', 'user']),
};

// TypeScript interface (after)
interface UserCardProps {
  name: string;
  age: number;
  email?: string;
  onClick: () => void;
  role?: 'admin' | 'user';
}
```

---

## Common Types Reference

```tsx
// Children
children: React.ReactNode           // anything renderable
children: React.ReactElement        // only JSX elements
children: JSX.Element               // single JSX element

// Style
style?: React.CSSProperties

// HTML props passthrough
props: React.HTMLAttributes<HTMLDivElement>
inputProps: React.InputHTMLAttributes<HTMLInputElement>

// Refs
ref: React.RefObject<HTMLInputElement>
ref: React.MutableRefObject<number>

// Context
const ThemeContext = createContext<Theme | null>(null);

// Discriminated unions
type Status =
  | { state: 'loading' }
  | { state: 'success'; data: User[] }
  | { state: 'error'; message: string };
```

---

## Companion Script
```bash
npx reactforge gen-typescript ./src
```
Reads existing PropTypes and generates TypeScript interfaces automatically.

---

## Migration Order (recommended)
1. Types/interfaces files first (`types.ts`)
2. Utility functions (pure functions are easiest)
3. Custom hooks
4. Leaf components (no children)
5. Container components
6. Root/App component last

