Surf‑Side Pair Review in Santa Teresa

Sunrise painted the Pacific pink while Andrés and I huddled over a hotspot at a Costa Rican surf hostel. His pull request introduced a new React modal, but the props object was typed as any. One mis‑spelled field crashed staging—minutes before a demo in Bogotá. We refactored on the spot: explicit interfaces, union guards, and a generic hook that eliminated three runtime checks. The demo shipped, and we still caught the morning waves. Today’s post distills the patterns that saved us that dawn.


Why Type Safety Matters in 2025

Component libraries grow faster than onboarding docs, and remote teams juggle time zones where a production bug might sleep eight hours before rescue. TypeScript bolts a static safety net onto React—catching prop mismatches, state shape drift, and brittle ref forwarding before code reaches CI. With server components and Suspense now mainstream, implicit any values sneak in from data‑fetching layers. Mastering type‑safe patterns keeps junior developers productive and senior reviewers sane—whether you’re coding on Dominican fiber or Colombian café Wi‑Fi.


Toolbelt at a Glance

Tool / ConceptOne‑liner purpose
TypeScript 5.4Superset of JS with static typing and inference.
@types/reactCommunity definitions that teach TypeScript React’s API.
ts‑nodeRun TS files directly; great for snippet experiments.
eslint‑plugin‑react‑hooksLints dependency arrays to avoid stale props.
CLI CommandWhat it does
npm i -D typescriptAdds the TypeScript compiler.
npx tsc --initGenerates a baseline tsconfig.json.
npm i -D @types/react @types/react-domInstalls type defs for plain JS packages.
npm i -D eslint @typescript-eslint/eslint-pluginLints TS + React code.

Concept Primer — Plain English

  1. Structural Typing: Objects are compatible by shape, not class—ideal for flexible props.
  2. Generics: Type “variables” that make hooks reusable without sacrificing safety.
  3. Discriminated Unions: Combine variants with a shared tag, enabling exhaustive switch checks.
  4. Utility Types: Helpers like Partial<T> or ComponentProps<'button'> reduce duplication.

Step‑by‑Step Patterns

1. Typed Component Props

tsxCopyEdit// Avatar.tsx
type AvatarProps = {
  src: string;
  alt: string;
  size?: number; // defaults work with ?
};

export function Avatar({ src, alt, size = 48 }: AvatarProps) {
  return <img src={src} alt={alt} width={size} height={size} />;
}

ExplanationAvatarProps surfaces required (src, alt) and optional (size) properties. IntelliSense guides juniors; CI blocks missing alt‑text.


2. Generic Hooks for Reusable State

tsxCopyEdit// useLocalStorage.ts
import { useState, useLayoutEffect } from 'react';

export function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    const cached = localStorage.getItem(key);
    return cached ? (JSON.parse(cached) as T) : initial;
  });

  useLayoutEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

Line‑by‑line


3. Discriminated Unions for API Responses

tsCopyEdittype Loading = { status: 'loading' };
type Error = { status: 'error'; message: string };
type Success<T> = { status: 'success'; data: T };

export type FetchState<T> = Loading | Error | Success<T>;
tsxCopyEditfunction UserCard({ userId }: { userId: string }) {
  const state = useUser(userId); // returns FetchState<User>

  switch (state.status) {
    case 'loading':
      return <>…</>;
    case 'error':
      return <p role="alert">{state.message}</p>;
    case 'success':
      return <p>{state.data.name}</p>;
  }
}

Why it rocks—Add a new variant ('stale') and TypeScript forces every switch to handle it, preventing uncaught null pointers in production.


4. Safer Polymorphic Components

tsxCopyEdit// Button.tsx
import { ElementType, ComponentProps } from 'react';

type Props<E extends ElementType> = {
  as?: E;
  variant?: 'primary' | 'ghost';
} & ComponentProps<E>;

export function Button<E extends ElementType = 'button'>({
  as,
  variant = 'primary',
  ...rest
}: Props<E>) {
  const Tag: ElementType = as || 'button';
  return <Tag className={`btn-${variant}`} {...rest} />;
}

Now <Button as="a" href="/about" /> gets href autocompletion, while <Button onClick={…}> stays strictly typed.


Common Pitfalls & Fixes

  1. React.FC Overuse
    React.FC forces implicit children and ReactNode return, hiding generic props. Fix: prefer plain function declarations with explicit return types.
  2. any Escape Hatches
    Temporary casts become permanent tech debt. Fix: pair‑review // @ts-expect-error annotations; set noImplicitAny to true.
  3. Stale Dependencies in Hooks
    Forgetting to list a dispatch callback in useEffect can create ghost state. ESLint with the hooks plugin catches this at commit time.

Remote‑Work Insight Box

During code reviews from Panama to Porto, TypeScript’s red squiggles bridge time gaps. A teammate in Brazil surfaces compile errors at 2 a.m. my time, so I wake to actionable comments instead of vague bug reports—accelerating asynchronous collaboration.


Performance & Accessibility Checkpoints


Wrapping Up — A Pattern Checklist

Type safety isn’t about pleasing a compiler; it’s about freeing mental RAM for design decisions and keeping remote teams aligned across oceans. Try a small refactor today—your future self will thank you from the next time zone.

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x