Skip to main content

Posts

Showing posts from August, 2025

🧱 Part 17 — Advanced TypeScript Patterns (Branded Types, Results, Exhaustive Checks) 🧠

When your app grows, types become your safety net . Here are advanced—but approachable—TypeScript patterns that prevent entire classes of bugs. Every snippet is commented and copy‑paste ready. 1) Branded (Opaque) Types — prevent mixing IDs/units Avoid passing a ProjectId where a TaskId is expected. // src/types/brand.ts // Create an opaque subtype of T (compile-time only) export type Brand<T, B extends string> = T & { readonly __brand: B }; export type TaskId = Brand<string, 'TaskId'>; export type ProjectId = Brand<string, 'ProjectId'>; export const TaskId = (s: string): TaskId => s as TaskId; export const ProjectId = (s: string): ProjectId => s as ProjectId; // Usage function loadTask(id: TaskId) {/* impl */} // loadTask('123'); // ❌ plain string not allowed loadTask(TaskId('123')); // ✅ branded Brands exist only at compile time; runtime values are plain strings. 2) Discriminated Unions + Exhaustiv...

🧱 Part 16 — Accessibility First (Semantics, Keyboard, ARIA, Focus, Color)

Accessibility (a11y) is not optional : it helps keyboard users, screen‑reader users, and frankly everyone (think: low contrast screens, bright sun, fatigue). This part is beginner‑friendly and packed with commented patterns you can paste in today. 🎯 Goals Semantic HTML & landmarks (nav, main, header, footer) Keyboard support (Tab order, focus outlines, ESC to close) Visible focus with :focus-visible Skip link , visually hidden text, live regions Readable colors (contrast), reduced motion ARIA for dialog , tabs , and form errors 1) Baseline: semantic layout + skip link Make the structure obvious to assistive tech and provide a quick jump to content. // src/app/Layout.tsx import { SkipLink } from '@/components/a11y/SkipLink'; export default function Layout({ children }: { children: React.ReactNode }) { return ( <div> <SkipLink target="#main" /> <header role="banner" className="p-4 border-b...

🧱 Part 15 — Code Splitting & Suspense (Lazy Routes, Error Boundaries, Preloading, Skeletons) 🧩

Big bundles make apps feel sluggish. Code splitting ships only what’s needed when it’s needed. Suspense gives you a clean way to show fallbacks (spinners/skeletons) while chunks/data load. This guide is beginner‑friendly and copy‑paste safe. 🎯 What you’ll do Split code at route and component level with React.lazy Wrap chunks in Suspense with polished fallbacks Add Error Boundaries so chunk or render failures don’t blank your app Preload chunks on hover/touch for instant nav Use Vite goodies like import.meta.glob for large lazy trees Show skeleton UIs instead of jumpy spinners Works with Vite + React Router 6+ (what we’ve been using). 1) Route‑level code splitting with React.lazy Each route becomes its own chunk. React loads it on demand. // src/app/AppRouter.tsx import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { Suspense, lazy } from 'react'; import Layout from '@/app/Layout'; import NotFound from '@/pages...

🧱 Part 14 — Performance Tuning (memo, useCallback, Profiler, Virtualization) ⚡️

Make your app feel snappy by avoiding unnecessary work. This part is practical, beginner‑friendly, and packed with commented examples you can paste in. πŸŽ›️ The Big Ideas Render less: memoize components and values so React doesn’t re-render needlessly. Do less: avoid heavy computations during render; cache them with useMemo . Work smarter: virtualize long lists so the DOM only contains visible rows. Measure first: use the React Profiler (and browser Performance tab) to verify improvements. Golden rule: Measure → Change → Measure again. 1) React.memo — skip re-renders when props didn’t change // src/components/perf/ExpensiveRow.tsx import React from 'react'; type RowProps = { id: string; title: string; done: boolean; onToggle: (id: string) => void; }; function RowBase({ id, title, done, onToggle }: RowProps) { // pretend this is heavy (expensive formatting, etc.) for (let i = 0; i < 200_000; i++); // demo CPU work return ( <div ...

🧱 Part 13 — Testing Strategy (Vitest + Testing Library + MSW) πŸ§ͺ

A great test setup is fast, reliable, and mirrors how users actually use your app. We’ll set up Vitest (fast runner), React Testing Library (test behavior, not internals), and MSW (mock server) — with clear, commented examples for components , hooks , and Redux slices , plus API integration tests. Philosophy: Test the public surface (what users see/do), not implementation details. Prefer queries by role/label/text over test IDs. Keep unit tests small; add a few integration tests where flows matter. 1) Install test tooling pnpm add -D vitest @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom pnpm add -D msw whatwg-fetch # MSW for API mocks, fetch polyfill for jsdom 2) Configure Vitest (Vite + jsdom + setup file) // vitest.config.ts import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], test: { environment: 'jsdom...

🧱 Part 12 — Storybook for React + TypeScript (Document & Test Your UI) πŸ“š

Now that we have design system primitives (Button, Input, Card), we need a way to document, preview, and test them in isolation. That’s where Storybook comes in. Storybook provides: A component playground outside your app. Interactive docs for your design system. Addons for accessibility (a11y) , viewport testing , and interaction testing . 1) Install Storybook Run the official setup tool: npx storybook@latest init Detects React + TypeScript + Vite automatically. Creates a .storybook/ folder with config. Adds example stories in src/stories/ . Start Storybook: pnpm storybook This launches on http://localhost:6006 . 2) Configure TypeScript support In .storybook/tsconfig.json , extend your root config: { "extends": "../tsconfig.json", "include": ["../src", "./*.ts", "./*.tsx"] } 3) Write your first story (Button) Stories live alongside components or in src/stories/ . Each story is a different s...

🧱 Part 11 — Design System & Theming (Tokens • CSS Variables • Tailwind • A11y) 🎨

A design system makes your UI consistent, accessible, and fast to iterate. We’ll define design tokens (colors, spacing, radii, typography) as CSS variables, wire light/dark themes , and build typed UI primitives (Button, Input, Card). Everything is copy‑paste ready and verified for errors. 🧩 Concepts in 30 seconds Tokens : the single source of truth for colors/spacing/typography. Primitives : small, reusable components that read tokens. Variants : controlled style options (e.g., primary | secondary | danger ). Theming : switch token values for light/dark without changing component code. 1) Tokens as CSS variables (light & dark) We use HSL/hex via var(--token) so classes can read them with Tailwind’s arbitrary values. /* src/styles/tokens.css */ :root { /* Base colors */ --color-bg: #ffffff; --color-fg: #111111; --color-muted: #6b7280; /* slate-500 */ --color-primary: #2563eb; /* blue-600 */ --color-danger: #dc2626; /* red-600 *...

🧱 Part 10 — Context API & Dependency Injection (Lightweight Global State) πŸͺ’

Sometimes you don’t need Redux—just a small, type‑safe way to share a value or service across components without prop‑drilling. That’s exactly what React Context is for. Below is a clean, error‑free , copy‑paste‑ready setup with thorough explanations and commented code . 🧩 Core Ideas Context : A container that can provide a value deep in the tree. Provider : A component that owns the value and makes it available to its descendants. useContext : A hook to read that value. Dependency Injection (DI) : Pass a service (e.g., API client) via Context so components can swap implementations (e.g., mock vs real). 1) Theme Context (with safe typing + memo + persistence) A minimal, production‑ready pattern: Throws a helpful error if used outside its provider. Memoizes the context value to avoid unnecessary re‑renders. Persists the theme to localStorage and respects system preference on first load. // src/contexts/ThemeContext.tsx import { createContext, useContext, useEffec...

🧱 Part 9 — Global State with Redux Toolkit (RTK + TypeScript) πŸ—‚️

Some state doesn't come from the server and shouldn't be fetched via HTTP—think theme (light/dark) , active filters , sidebar open/closed , currently selected task ID , etc. This is global/app state . For this, Redux Toolkit (RTK) gives you a clean, predictable, and type‑safe pattern. Quick mental model: React state → great for local component-only state. Redux (RTK) → great for app-wide UI state that multiple components need. TanStack Query → great for server data (caching, refetching, syncing). 🧩 Core Concepts (explained simply) Store : a single object tree that holds your app’s global state. Slice : a focused piece of state (e.g., ui , filters ) with its reducers and actions created by RTK. Reducer : a pure function (state, action) → newState . In RTK you write mutating-looking code; under the hood Immer produces an immutable update for you. Action : a plain object { type, payload } describing what happened . Selector : a function that reads/der...

🧱 Part 8 — Server State with TanStack Query (Typed Queries & Mutations) ⚙️✨

Client state (local UI) ≠ server state (data from APIs). TanStack Query (a.k.a. React Query) makes server state easy, fast, and type‑safe: caching, retries, background refresh, optimistic updates, and more. 🎯 Goals Install & configure QueryClient with sensible defaults Create typed query/mutation hooks that wrap our Axios API Use cache keys , staleTime , select , and placeholderData Implement optimistic updates with rollback Add pagination and infinite scrolling patterns We’ll reuse the Axios layer from Part 7 ( /lib/api ). 0) Install pnpm add @tanstack/react-query 1) Provider setup (once at the app root) // src/app/AppProviders.tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { ReactNode } from 'react'; const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60_000, refetchOnWindowFocus: true, retry: (failureCount, error: any) => (error?.status === 404 ...

🧱 Part 7 — HTTP & Data Fetching with Axios (Beginner‑Friendly, Fully Typed) 🌐

Real apps talk to servers. In this part, we’ll build a clean, typed HTTP layer using Axios and wire it to our UI. We’ll explain every piece and add comments right in the code so it’s copy‑paste ready. 🎯 Goals One shared Axios client with sensible defaults Typed request/response/error shapes DTO ↔ Domain mapping (convert server shapes to UI‑friendly types) Cancellation with AbortController A tiny retry helper (with exponential backoff) A commented TaskList that fetches & toggles tasks Bonus: a minimal mock for local testing 0) Install (if needed) pnpm add axios pnpm add -D axios-mock-adapter # optional, for local API mocks 1) Shared Axios client (one place for baseURL, headers, errors) // src/lib/api/http.ts import axios from 'axios'; // A simple, typed error shape we’ll use across the app export type ApiError = { status: number; message: string }; // Create a single Axios instance used everywhere export const http = axios.create({ baseURL: import.meta....