Deck 12 - Advanced TypeScript for React (Senior TS Patterns) Flashcards

Senior TS patterns for React: sound typing, unions/narrowing, generics, runtime validation, safe component APIs, and review-style verdict wording. (80 cards)

1
Q

Deck 12 - Advanced TypeScript for React (Senior TS Patterns) (objective)

A

Goal: write and review React+TS like a senior: sound types, safe boundaries, expressive component APIs, and correct narrowing.
You learn: what/why, common traps, and what to recommend during code review.
Output style: PASS/PARTIAL/FAIL + 2-3 issues + fix approach.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Mental model: TypeScript is structural, not nominal (what this means).

A

Types are compatible based on shape, not name. If two types have the same structure, they are assignable.
Why it matters: accidental compatibility can happen; use branded types when you need nominal-like safety.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Mental model: TS checks at compile time, not runtime.

A

Types disappear at runtime. If data comes from outside (API, localStorage), you must validate at runtime.
Best practice: parse/validate at boundaries (Zod/io-ts) before using data in React state.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Mental model: ‘unknown’ vs ‘any’ (how to choose).

A

unknown is safe: you must narrow before using. any disables type checking.
Senior rule: prefer unknown at boundaries; avoid any except in narrow interoperability seams.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Mental model: variance and why callbacks bite.

A

Function parameter types are (effectively) contravariant/bivariant in some React typings.
Why: you can accidentally accept too-broad/too-narrow handlers. Prefer explicit handler types and generic props when needed.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Mental model: widening vs literal types.

A

const x = ‘a’ preserves literal ‘a’; let x = ‘a’ widens to string.
Why: literal types enable discriminated unions and safer component APIs.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Mental model: inference is your friend, but be explicit at boundaries.

A

Let TS infer locals; be explicit at component boundaries (props, return values of hooks, public APIs).
Why: keeps code readable and prevents accidental breaking changes.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Mental model: type-level code has cost (cognitive + compile time).

A

Overly clever conditional types can slow TS and confuse reviewers.
Best practice: prefer simple types unless complexity buys real safety.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Mental model: narrowing is driven by control flow.

A

TS narrows based on checks like typeof, in, instanceof, and discriminants.
Senior: structure code to make narrowing obvious and local.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Mental model: ‘satisfies’ keeps literals while checking shape.

A

Use ‘satisfies’ to ensure an object meets a type without widening the object’s inferred literal types.
Great for configuration maps and component prop dictionaries.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Mental model: generics are about relationships.

A

Generic types express ‘this depends on that’. Example: a function that returns the same element type it receives.
Senior: use generics to model relationships, not to ‘avoid writing types’.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Mental model: discriminated unions model state machines.

A

Use a shared literal field (status/type) to represent distinct states.
Why: prevents impossible states and improves React UI branching.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Mental model: never trust API response shapes.

A

Even if backend is ‘typed’, responses can change, be partial, or error.
Fix: validate + handle missing fields; model error paths explicitly.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Mental model: avoid ‘boolean soup’ in props.

A

Multiple booleans create invalid combinations and unclear meaning.
Prefer enums/unions (variant: ‘primary’|’secondary’) or discriminated props.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Mental model: prefer readonly for inputs.

A

Mark props and shared objects readonly to prevent mutation bugs.
Why: mutation breaks memoization and surprises consumers.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Mental model: null vs undefined (be consistent).

A

Pick a consistent meaning: undefined often means ‘not provided’; null often means ‘known empty’.
Senior: be consistent in React state and API models.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Mental model: Exhaustiveness is a correctness feature.

A

Use exhaustive checks in switch on unions to catch missing cases at compile time.
Why: prevents runtime bugs when new variants are added.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Mental model: prefer domain types over primitives (when it helps).

A

Instead of string for everything, use types like UserId, Email.
Why: reduces accidental mixing of IDs/values.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

Mental model: React types can lie if you force them.

A

Casting (as) can silence errors but create runtime bugs.
Senior: only cast after validating, and keep casts narrow and well-justified.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Mental model: build type safety from the edges inward.

A

Start with typed API client + runtime validation, then typed hooks, then typed components.
Why: prevents ‘any leaks’ from contaminating your app.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

Best practice: type props with an explicit Props type.

A

Define Props separately and give components a single named prop type.
Why: improves readability, reusability, and keeps component signatures stable.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

Best practice: do NOT use React.FC by default (why).

A

React.FC adds implicit children and can complicate generics/defaultProps.
Prefer: function Component(props: Props) or const Component = (props: Props) => …

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

Best practice: type state as a union that matches reality.

A

Example: User | null, or {status:’loading’} | {status:’success’, data:User}.
Why: prevents impossible states and makes render branching safe.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

Best practice: use discriminated union for async state.

A

Represent async state with status: ‘idle’|’loading’|’error’|’success’.
Why: TS can narrow state so UI code is correct by construction.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
Best practice: prefer unknown for fetch results, then parse.
Treat JSON as unknown. Validate with a schema before setting state. Why: avoids runtime crashes from shape mismatch.
26
Best practice: use const assertions for config maps.
Use as const or satisfies to build typed lookup tables. Why: keeps literal keys/values and enables safe indexing.
27
Best practice: event handler typing.
Use React types: React.MouseEvent, React.ChangeEvent. Why: correct target/value types and fewer unsafe casts.
28
Best practice: refs - type them explicitly.
Use useRef(null) or useRef(null). Why: prevents accidental undefined/null misuse and unsafe property access.
29
Best practice: avoid non-null assertions (!) in UI code.
Non-null assertion can crash at runtime. Prefer: guard, optional chaining, or restructure so TS can narrow.
30
Best practice: encode optionality in props clearly.
If prop is optional, code must handle it. If required, make it required. Why: optional props become a reliability footgun when callers omit them.
31
Best practice: use overloads sparingly for hooks/utilities.
Overloads can make APIs nicer but can also confuse inference. Prefer a single generic signature unless you truly need different call shapes.
32
Best practice: prefer type-safe enums via unions, not TS enum.
Use 'type Variant = "a" | "b"' instead of enum. Why: unions are simpler and tree-shake well.
33
Best practice: never use 'string' for CSS class names map keys if you can avoid it.
Type class maps with literal keys: const styles = { root: '...', ... } as const. Why: prevents typos and provides autocomplete.
34
Best practice: constrain generics to what you need.
Use instead of if you require id. Why: expresses constraints; prevents misuse.
35
Best practice: component 'variant' prop with discriminated props.
Use unions to vary allowed props by variant. Why: prevents invalid prop combos (e.g., href only allowed for links).
36
Best practice: never widen union accidentally with '| any'.
Adding any to a union collapses safety. Senior: avoid any; keep unions precise.
37
Best practice: model API errors explicitly.
Use { ok:true, data } | { ok:false, error }. Why: forces caller to handle error path; avoids try/catch-only flows.
38
Best practice: prefer readonly arrays for inputs.
Use readonly T[] for function inputs and props. Why: prevents accidental mutation and helps reasoning.
39
Best practice: use 'satisfies' for typed objects you want to keep narrow.
Example: const routes = { home:'/', ... } satisfies Record. Why: checks shape without widening.
40
Best practice: do not over-export types from components.
Export only what consumers need. Why: avoids tight coupling and reduces refactor cost.
41
Pattern: branded types for IDs (what/why).
Use a brand to prevent mixing different IDs (UserId vs OrderId). Why: TS is structural, so plain strings are interchangeable unless branded.
42
Pattern: indexed access types (use case).
Use T['field'] to reference the type of a property. Why: keeps types in sync when object shapes change.
43
Pattern: mapped types (use case).
Transform keys: { [K in keyof T]: ... }. Why: build derived types like partials, readonly, and field validators.
44
Pattern: conditional types (use case).
Type depends on condition: T extends U ? X : Y. Why: express relationships like 'if input is array, output is element type'. Keep it simple.
45
Pattern: infer in conditional types (use case).
Extract a type: T extends Promise ? U : never. Why: decode wrapper types in utilities.
46
Pattern: satisfies vs as const (difference).
as const freezes literals and makes everything readonly. satisfies checks shape but keeps inference for literals without forcing readonly everywhere.
47
Pattern: exact object shapes (common pitfall).
Excess property checks happen on object literals but not on variables. Senior: avoid relying on excess property checks for security; validate at runtime.
48
Pattern: exhaustive switch with never.
In a switch on a union, assign the default to a never variable. Why: TS errors when a new union member is unhandled.
49
Pattern: safe narrowing with 'in'.
Use 'if ("error" in res)' to narrow union shapes. Why: good for API result unions and polymorphic objects.
50
Pattern: user-defined type guards (when).
Write function isUser(x: unknown): x is User. Why: reusable runtime checks that inform TS narrowing.
51
Pattern: generics in components (best practice).
Prefer inference-friendly patterns: (props: Props) => ... Why: React JSX can misparse ; use comma trick and keep generic APIs simple.
52
Pattern: building typed dictionaries safely.
Use Record where K is a union of allowed keys. Why: prevents missing keys and avoids string indexing errors.
53
Pattern: avoiding 'as' with helper functions.
Prefer createX(...) helpers that return correctly typed values. Why: avoids unsafe casts and centralizes invariants.
54
Pattern: type-safe error handling with Result.
Use Result = { ok:true, value:T } | { ok:false, error:E }. Why: explicit control flow; better than throwing for expected errors.
55
Pattern: noImplicitAny + strictNullChecks (why).
These flags enforce sounder code by preventing implicit any and forcing null handling. Senior: treat type errors as correctness bugs, not nuisance.
56
Under the hood: why JSON is unknown at runtime.
fetch().json() returns untyped data. TS cannot guarantee shape. You must validate because runtime can differ from compile-time assumptions.
57
Under the hood: why union narrowing improves correctness.
Control-flow narrowing means once you check status/ok, TS knows which fields exist. This prevents calling methods on possibly undefined fields in React renders.
58
Under the hood: why 'any' spreads like a virus.
Once a value is any, operations on it become any, removing checks downstream. Senior: contain any to the smallest boundary, or use unknown + parsing.
59
Under the hood: why non-null assertion is dangerous in React.
React renders can happen before data loads; assumptions like user!.name can crash. Prefer guards/conditional rendering or state machines.
60
Under the hood: why structural typing can cause accidental bugs.
Two distinct domain values may share shape (string), so TS allows mixing. Branding or wrapper objects prevent accidental assignment.
61
Under the hood: why 'satisfies' is powerful for config objects.
It checks that your object meets a contract but keeps literal inference. This gives you safe indexing + autocomplete without widening everything to string.
62
Under the hood: why TS enums can be annoying.
They emit runtime code and can be harder to tree-shake. String unions often provide the same benefit without runtime output.
63
Under the hood: JSX generic parsing gotcha.
In TSX, can be parsed as a JSX tag. Workaround: or move generics to helper functions/hooks.
64
Under the hood: why exhaustive checks prevent regressions.
When you add a new union variant, TS forces you to handle it. This catches missing UI branches during refactors.
65
Under the hood: why 'readonly' helps performance reasoning.
Readonly encourages immutability, which pairs with referential equality checks. Immutable data makes memoization and rerender analysis simpler.
66
Gotcha: using any for API response. ``` const [user, setUser] = useState(null); useEffect(() => { fetch('/api/user').then(r => r.json()).then(setUser); }, []); return
{user.name.toUpperCase()}
; ```
FAIL - any hides unsafe API shape and allows runtime crashes (user/name may be missing). Use unknown + validation and type state as User | null (or status union).
67
Gotcha: missing null in state union. ``` type User = { id: string; name: string }; const [user, setUser] = useState({ id: '', name: '' }); ```
PARTIAL - Sentinel empty user can mask loading vs real data. Prefer User | null or discriminated async state.
68
Gotcha: optional prop but treated as required. ``` type Props = { userId?: string }; function Profile({ userId }: Props) { return
{userId.toUpperCase()}
; } ```
FAIL - userId may be undefined. Make prop required or add guard + fallback UI.
69
Gotcha: unsafe non-null assertion. ``` return
{user!.name}
; ```
FAIL - Can crash before data is loaded. Add conditional rendering or model state so TS narrows safely.
70
Gotcha: union without narrowing. ``` type Api = { ok: true; user: { name: string } } | { ok: false; error: string }; function f(res: Api) { return res.user.name; } ```
FAIL - res.user does not exist on error case. Narrow with if (res.ok) or 'in' operator.
71
Gotcha: Result type but forgetting exhaustiveness. ``` type State = { status: 'loading' } | { status: 'success'; data: User } | { status: 'error'; message: string }; function View(s: State) { if (s.status === 'success') return s.data.name; return '...'; } ```
PARTIAL - Missing explicit handling of loading/error. Prefer switch with exhaustive check for clarity.
72
Gotcha: stringly-typed variant booleans. ``` type Props = { primary?: boolean; secondary?: boolean }; ```
PARTIAL - Invalid combinations possible. Use variant union: { variant: 'primary' | 'secondary' }.
73
Gotcha: TSX generic parsing issue. ``` const Comp = (props: { value: T }) =>
; ```
FAIL - In TSX, can be parsed as JSX. Use or move generic to helper function.
74
Gotcha: unsafe cast to satisfy compiler. ``` const user = data as User; setUser(user); ```
PARTIAL - Cast can hide invalid shapes. Prefer runtime validation or a type guard before assignment.
75
Gotcha: using Record loses key safety. ``` const labels: Record = { open: 'Open' }; labels.opne; ```
PARTIAL - Typos not caught. Prefer literal key union + satisfies to keep keys narrow.
76
Gotcha: mixing null and undefined in state inconsistently.
PARTIAL - Inconsistent optionality makes checks messy. Pick a convention: null for 'empty', undefined for 'not provided' and stick to it.
77
Gotcha: mutating typed arrays passed via props. ``` function List({ items }: { items: string[] }) { items.push('x'); return null; } ```
FAIL - Mutates props; can break memoization and surprises callers. Treat as readonly input and create a new array.
78
Gotcha: missing readonly in helper signature. ``` function sortUsers(users: User[]) { return users.sort(...); } ```
PARTIAL - Mutates input. Use readonly User[] and return a new sorted copy.
79
Gotcha: event target typing mistake. ``` function onChange(e: Event) { // e.target.value } ```
FAIL - Wrong event type in React. Use React.ChangeEvent for correct target typing.
80
Gotcha: async function returns Promise but typed as value. ``` function load(): User { return fetch('/api').then(r => r.json()); } ```
FAIL - Return type mismatch. Use Promise and validate response shape.