🎠TypeScript Advanced: Teaching Your Code to Speak Clearly
The Magical Translator Analogy: Imagine you have a super-smart robot helper. But this robot only understands instructions when you’re VERY specific. TypeScript is like giving your robot a special dictionary that says “this button click means THIS” or “this box can only hold numbers, not words.” The more specific you are, the fewer mistakes your robot makes!
🎣 Typing Hooks: Labeling Your Magic Boxes
Think of React hooks like magic boxes that remember things for you. But these boxes need labels so they know what to hold!
useState: The Memory Box
Imagine a box that remembers a number for you:
// Tell the box: "You hold numbers!"
const [age, setAge] = useState<number>(0);
// Tell the box: "You might hold a user or nothing"
const [user, setUser] = useState<User | null>(null);
Why labels matter:
- Without labels, the box gets confused
- With labels, it stops you from putting a cat in the “numbers only” box
useEffect: The Action Timer
useEffect(() => {
// This runs after your component appears
document.title = "Hello!";
// Return a cleanup function (optional)
return () => {
document.title = "Goodbye!";
};
}, []); // Empty array = run once
useReducer: The Smart Decision Maker
type State = { count: number };
type Action =
| { type: 'add'; amount: number }
| { type: 'reset' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'add':
return { count: state.count + action.amount };
case 'reset':
return { count: 0 };
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
graph TD A["Action Arrives"] --> B{What type?} B -->|add| C["Add amount to count"] B -->|reset| D["Set count to zero"] C --> E["New State"] D --> E
🎯 Typing Events: Naming Your Actions
When you click a button or type in a box, events happen. TypeScript wants to know EXACTLY what kind of event!
Button Clicks
// The click event knows about buttons
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
console.log("Button clicked!");
e.preventDefault(); // Stop default behavior
}
<button onClick={handleClick}>Click Me!</button>
Typing in Input Boxes
// The change event knows about inputs
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const value = e.target.value; // TypeScript knows this is a string!
console.log("You typed:", value);
}
<input onChange={handleChange} />
Form Submissions
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault(); // Don't reload the page!
console.log("Form sent!");
}
<form onSubmit={handleSubmit}>
<button type="submit">Send</button>
</form>
Common Event Types Cheat List
| Event | Type | When It Happens |
|---|---|---|
| Click | MouseEvent<HTMLButtonElement> |
Button pressed |
| Type | ChangeEvent<HTMLInputElement> |
Text entered |
| Submit | FormEvent<HTMLFormElement> |
Form sent |
| Key | KeyboardEvent<HTMLInputElement> |
Key pressed |
📍 Typing Refs: Pointing at Real Things
Refs are like putting a sticky note on something so you can find it later. But you need to say WHAT you’re putting the note on!
Pointing at HTML Elements
// "I'm pointing at an input box (or nothing yet)"
const inputRef = useRef<HTMLInputElement>(null);
function focusInput() {
// The "?" means "only if it exists"
inputRef.current?.focus();
}
return <input ref={inputRef} />;
Storing Values That Don’t Trigger Updates
// Keep a timer ID without re-rendering
const timerRef = useRef<number>(0);
useEffect(() => {
timerRef.current = window.setInterval(() => {
console.log("Tick!");
}, 1000);
return () => clearInterval(timerRef.current);
}, []);
graph TD A["Create Ref"] --> B["Point to Element"] B --> C["Access via .current"] C --> D{Does it exist?} D -->|Yes| E["Use it!"] D -->|No| F["Do nothing safely"]
đź§© Generic Components: One Size Fits Many
Imagine a gift box that can hold ANY type of gift, but once you say “this box is for toys,” it only accepts toys!
The Dropdown That Works for Anything
// T is a placeholder for "any type"
type DropdownProps<T> = {
items: T[];
onSelect: (item: T) => void;
renderItem: (item: T) => string;
};
function Dropdown<T>(props: DropdownProps<T>) {
return (
<select onChange={(e) => {
const index = Number(e.target.value);
props.onSelect(props.items[index]);
}}>
{props.items.map((item, i) => (
<option key={i} value={i}>
{props.renderItem(item)}
</option>
))}
</select>
);
}
Using the Generic Dropdown
// For strings
<Dropdown
items={["Apple", "Banana", "Cherry"]}
onSelect={(fruit) => console.log(fruit)}
renderItem={(fruit) => fruit}
/>
// For objects
type User = { id: number; name: string };
<Dropdown<User>
items={[{ id: 1, name: "Alice" }]}
onSelect={(user) => console.log(user.id)}
renderItem={(user) => user.name}
/>
A List Component That Shows Anything
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map(renderItem)}</ul>;
}
🛠️ Utility Types: TypeScript’s Magic Wand
TypeScript has built-in tools that transform types. Think of them as magic spells!
Partial: “Maybe Some, Maybe None”
Makes all properties optional:
type User = {
name: string;
age: number;
email: string;
};
// Now all fields are optional!
type PartialUser = Partial<User>;
// This is now valid:
const update: PartialUser = { name: "Bob" };
Required: “Everything, No Exceptions”
Makes all properties required:
type MaybeUser = {
name?: string;
age?: number;
};
// Now ALL fields are required!
type FullUser = Required<MaybeUser>;
Pick: “I Only Want These”
Select specific properties:
type User = {
id: number;
name: string;
email: string;
password: string;
};
// Only id and name
type PublicUser = Pick<User, 'id' | 'name'>;
Omit: “Everything Except These”
Remove specific properties:
type User = {
id: number;
name: string;
password: string;
};
// Everything except password
type SafeUser = Omit<User, 'password'>;
Record: “A Dictionary of Things”
Create an object type with specific keys and values:
// Keys are strings, values are numbers
type Scores = Record<string, number>;
const gameScores: Scores = {
"level1": 100,
"level2": 250
};
ReturnType: “What Does This Function Give Back?”
function getUser() {
return { name: "Alice", age: 30 };
}
// Automatically figures out the return type!
type User = ReturnType<typeof getUser>;
// Result: { name: string; age: number }
graph TD A["Original Type"] --> B{Which Utility?} B -->|Partial| C["All Optional"] B -->|Required| D["All Required"] B -->|Pick| E["Keep Some"] B -->|Omit| F["Remove Some"] B -->|Record| G["Key-Value Map"]
🎮 Putting It All Together
Here’s a mini-app using EVERYTHING we learned:
// Utility types for user
type User = {
id: number;
name: string;
email: string;
};
type UserPreview = Pick<User, 'id' | 'name'>;
// Generic list component
function UserList<T extends UserPreview>({
users,
onSelect
}: {
users: T[];
onSelect: (user: T) => void;
}) {
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => onSelect(user)}>
{user.name}
</li>
))}
</ul>
);
}
// Main component with typed hooks and events
function App() {
const [selected, setSelected] = useState<User | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={handleClick}>Focus</button>
<UserList
users={[{ id: 1, name: "Alice", email: "a@b.c" }]}
onSelect={setSelected}
/>
</div>
);
}
🌟 Key Takeaways
- Typing Hooks = Tell your magic boxes what they can hold
- Typing Events = Name your actions precisely
- Typing Refs = Point clearly at real things
- Generic Components = Build reusable pieces that work with any type
- Utility Types = Transform types with magic spells
đź’ˇ Remember: TypeScript is your friend, not your enemy. It catches mistakes BEFORE they happen, like a spell-checker for your code logic!
You’re now a TypeScript wizard! Go forth and type safely! 🧙‍♂️✨
