RefPad

React

React 19 / TypeScript 5.x

Table of Contents

  1. Common Types
  2. Component Type Definitions
  3. Props Type Definitions
  4. Conditional Rendering
  5. List Rendering
  6. useState
  7. useEffect
  8. useRef
  9. useContext
  10. useReducer
  11. useMemo / useCallback
  12. Custom Hooks
  13. Event Handler Types
  14. Generics
  15. Utility Types

1. Common Types

Primitive / Basic Types

TypeDescription
stringString
numberNumber (integer or decimal)
booleantrue / false
nullExplicitly represents no value
undefinedUninitialized / not defined
string | nullUnion type (either type)
string[] / Array<string>Array
[string, number]Tuple (fixed order and types)
Record<string, number>Object with specified key and value types

React-Specific Types

TypeDescription
ReactNodeAny value that can be passed as children (JSX, string, number, boolean, null, etc.)
ReactElementJSX element only (excludes strings and numbers)
ComponentPropsWithoutRef<"button">Gets Props type of an HTML element
CSSPropertiesType for style attribute (React.CSSProperties)
RefObject<HTMLInputElement>Type for useRef
Dispatch<SetStateAction<T>>Type for setState

Event Handler Types

TypeCommon Use
ChangeEvent<HTMLInputElement>onChange for input
ChangeEvent<HTMLTextAreaElement>onChange for textarea
ChangeEvent<HTMLSelectElement>onChange for select
FormEvent<HTMLFormElement>onSubmit for form (with preventDefault)
MouseEvent<HTMLButtonElement>onClick for button
KeyboardEvent<HTMLInputElement>onKeyDown / onKeyUp

(See 13. Event Handler Types for code examples)


2. Component Type Definitions

Function Component

const MyComponent = () => {
  return <div>Hello</div>;
};
 
export default MyComponent;

Component Accepting Children

import { ReactNode } from "react";
 
type Props = {
  children: ReactNode;
};
 
const Wrapper = ({ children }: Props) => {
  return <div className="wrapper">{children}</div>;
};

3. Props Type Definitions

Basic Props

type ButtonProps = {
  label: string;
  onClick: () => void;
  disabled?: boolean;
  variant?: "primary" | "secondary";
};
 
const Button = ({
  label,
  onClick,
  disabled = false,
  variant = "primary",
  ...props
}: ButtonProps) => {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={variant}
      {...props}
    >
      {label}
    </button>
  );
};

Extending HTML Element Props

import type { ComponentPropsWithoutRef } from "react";
 
// Common pattern for wrapper components (adds custom props to input)
type InputProps = ComponentPropsWithoutRef<"input"> & {
  label: string;
  error?: string;
};
 
const Input = ({ label, error, ...props }: InputProps) => {
  return (
    <div>
      <label>{label}</label>
      <input {...props} />
      {error && <span>{error}</span>}
    </div>
  );
};

4. Conditional Rendering

&& Operator (render only when condition is true)

const Notification = ({ count }: { count: number }) => {
  return (
    <div>
      {/* Render only when count > 0 */}
      {count > 0 && <span>Notifications: {count}</span>}
 
      {/* Anti-pattern: renders "0" when count is 0 */}
      {count && <span>Notifications: {count}</span>}
 
      {/* Fix: convert to Boolean */}
      {!!count && <span>Notifications: {count}</span>}
    </div>
  );
};

Ternary Operator (render one or the other)

const AuthButton = ({ isLoggedIn }: { isLoggedIn: boolean }) => {
  return (
    <div>
      {isLoggedIn ? <button>Logout</button> : <button>Login</button>}
    </div>
  );
};

Variable Assignment (complex conditionals)

import { ReactNode } from "react";
 
type Status = "loading" | "error" | "success";
 
const StatusView = ({ status }: { status: Status }) => {
  let content: ReactNode;
 
  if (status === "loading") {
    content = <p>Loading...</p>;
  } else if (status === "error") {
    content = <p style={{ color: "red" }}>An error occurred</p>;
  } else {
    content = <p>Done!</p>;
  }
 
  return <div>{content}</div>;
};

Early Return (Guard Clause)

const UserProfile = ({ user }: { user: User | null }) => {
  if (!user) return <p>User not found</p>;
  if (!user.isActive) return <p>This user is inactive</p>;
 
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
};

Return null to Render Nothing

const Banner = ({ show }: { show: boolean }) => {
  if (!show) return null;
  return <div className="banner">Announcement!</div>;
};

5. List Rendering

Basic (map + key)

type Item = { id: number; name: string };
 
const ItemList = ({ items }: { items: Item[] }) => {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};

Key Rules and Gotchas

// Do not use index as key when list items can be sorted, added, or removed
<ul>
  {items.map((item, index) => (
    <li key={index}>{item.name}</li>
  ))}
</ul>
 
// Use a stable, unique ID (recommended)
<ul>
  {items.map((item) => (
    <li key={item.id}>{item.name}</li>
  ))}
</ul>
 
// index is acceptable only for static lists where order never changes

Extracting to a Component / Empty List Handling

// When splitting into a separate component, put key on the call site
const ItemCard = ({ item }: { item: Item }) => <li>{item.name}</li>;
 
const ItemList = ({ items }: { items: Item[] }) => {
  if (items.length === 0) return <p>No items found</p>;
 
  return (
    <ul>
      {items.map((item) => (
        <ItemCard key={item.id} item={item} />
      ))}
    </ul>
  );
};

6. useState

// Type inference works when initial value is obvious
const [count, setCount] = useState(0);
const [name, setName] = useState("Alice");
 
// Type definition (define the type before useState when explicit)
type User = {
  id: number;
  name: string;
  email: string;
};
 
// Explicit type annotation
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
 
// Update examples
setUser({ id: 1, name: "Bob", email: "bob@example.com" });
setUser(null);
 
// Functional update
setCount((prev) => prev + 1);

7. useEffect

import { useEffect, useState } from "react";
 
// Basic
useEffect(() => {
  console.log("Runs on mount");
}, []);
 
// With dependency array
useEffect(() => {
  console.log("Runs when count changes:", count);
}, [count]);
 
// Cleanup
useEffect(() => {
  const timer = setInterval(() => {
    setCount((c) => c + 1);
  }, 1000);
 
  return () => clearInterval(timer); // Runs on unmount
}, []);
 
// Async (cannot use async directly in useEffect)
useEffect(() => {
  const fetchData = async () => {
    const res = await fetch("/api/data");
    const json = await res.json();
    setData(json);
  };
 
  fetchData();
}, []);

8. useRef

import { useRef } from "react";
 
// DOM reference
const inputRef = useRef<HTMLInputElement>(null);
 
const handleFocus = () => {
  inputRef.current?.focus(); // Safe access with optional chaining
};
 
// Mutable value (does not trigger re-render)
const countRef = useRef<number>(0);
countRef.current += 1;
 
// Usage in JSX
<input ref={inputRef} type="text" />;

9. useContext

import { createContext, useContext, useState, ReactNode } from "react";
 
// 1. Type definition
type ThemeContextType = {
  theme: "light" | "dark";
  toggleTheme: () => void;
};
 
// 2. Create context
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
 
// 3. Safe access via custom hook
const useTheme = (): ThemeContextType => {
  const context = useContext(ThemeContext);
  if (!context) throw new Error("useTheme must be used within ThemeProvider");
  return context;
};
 
// 4. Create provider
const ThemeProvider = ({ children }: { children: ReactNode }) => {
  const [theme, setTheme] = useState<"light" | "dark">("light");
 
  const toggleTheme = () =>
    setTheme((prev) => (prev === "light" ? "dark" : "light"));
 
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};
 
// 5. Consumer
const Header = () => {
  const { theme, toggleTheme } = useTheme();
  return <button onClick={toggleTheme}>Current: {theme}</button>;
};

10. useReducer

import { useReducer } from "react";
 
// Type definitions
type State = {
  count: number;
  error: string | null;
};
 
type Action =
  | { type: "increment" }
  | { type: "decrement" }
  | { type: "reset"; payload: number }
  | { type: "setError"; payload: string };
 
// Reducer
const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + 1 };
    case "decrement":
      return { ...state, count: state.count - 1 };
    case "reset":
      return { ...state, count: action.payload };
    case "setError":
      return { ...state, error: action.payload };
    default:
      return state;
  }
};
 
// Usage
const Counter = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0, error: null });
 
  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "reset", payload: 0 })}>
        Reset
      </button>
    </div>
  );
};

11. useMemo / useCallback

import { useMemo, useCallback, memo, useState } from "react";
 
const items = [{ active: true }, { active: false }];
const [selectedId, setSelectedId] = useState(0);
 
// useMemo: memoize a computed value
const expensiveValue = useMemo(() => {
  return items.filter((item) => item.active).length;
}, [items]); // Recomputes only when items changes
 
// useCallback: memoize a function (useful when passing to child components as props)
const handleClick = useCallback((id: number) => {
  setSelectedId(id);
}, []); // No deps → same function reference every render
 
// Combine with child component optimization
const ChildComponent = memo(
  ({ onClick }: { onClick: (id: number) => void }) => {
    return <button onClick={() => onClick(1)}>Click</button>;
  },
);

12. Custom Hooks

import { useState, useEffect } from "react";
 
// Custom hook for data fetching
type FetchState<T> = {
  data: T | null;
  loading: boolean;
  error: string | null;
};
 
const useFetch = <T,>(url: string): FetchState<T> => {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
 
  useEffect(() => {
    const controller = new AbortController();
 
    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        const res = await fetch(url, { signal: controller.signal });
        if (!res.ok) throw new Error("Fetch failed");
        const json: T = await res.json();
        setData(json);
      } catch (e) {
        // Do not update state if aborted (unmount or url change)
        if (e instanceof Error && e.name === "AbortError") return;
        setError(e instanceof Error ? e.message : "Unknown error");
      } finally {
        setLoading(false);
      }
    };
 
    fetchData();
 
    return () => controller.abort();
  }, [url]);
 
  return { data, loading, error };
};
 
// Usage
type Post = { id: number; title: string };
 
const Posts = () => {
  const { data, loading, error } = useFetch<Post[]>("/api/posts");
 
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
 
  return (
    <ul>
      {data?.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
};

13. Event Handler Types

import { ChangeEvent, FormEvent, MouseEvent, KeyboardEvent } from "react";
 
// input onChange
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};
 
// textarea onChange
const handleTextChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
  console.log(e.target.value);
};
 
// select onChange
const handleSelect = (e: ChangeEvent<HTMLSelectElement>) => {
  console.log(e.target.value);
};
 
// form onSubmit
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  // handle form
};
 
// button onClick
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
  console.log(e.currentTarget);
};
 
// keyboard event
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
  if (e.key === "Enter") {
    // handle Enter key
  }
};

Usage in JSX

<input onChange={handleChange} onKeyDown={handleKeyDown} />
<form onSubmit={handleSubmit}>
  <button onClick={handleClick}>Submit</button>
</form>

14. Generics

import { ReactNode } from "react";
 
// Generic list component
type ListProps<T> = {
  items: T[];
  renderItem: (item: T) => ReactNode;
  keyExtractor: (item: T) => string | number;
};
 
const List = <T,>({ items, renderItem, keyExtractor }: ListProps<T>) => {
  return (
    <ul>
      {items.map((item) => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
};
 
// Usage
type User = { id: number; name: string };
 
<List<User>
  items={users}
  keyExtractor={(u) => u.id}
  renderItem={(u) => <span>{u.name}</span>}
/>;

15. Utility Types

type User = {
  id: number;
  name: string;
  email: string;
  age: number;
};
 
// Partial: make all properties optional
type PartialUser = Partial<User>;
// → { id?: number; name?: string; ... }
 
// Required: make all properties required
type RequiredUser = Required<PartialUser>;
 
// Pick: extract specific properties
type UserPreview = Pick<User, "id" | "name">;
// → { id: number; name: string }
 
// Omit: exclude specific properties
type UserWithoutEmail = Omit<User, "email">;
// → { id: number; name: string; age: number }
 
// Readonly: make all properties read-only
type ReadonlyUser = Readonly<User>;
 
// Record: object type with specified key and value types
type RoleMap = Record<"admin" | "user" | "guest", boolean>;
 
// ReturnType: get the return type of a function
const getUser = () => ({ id: 1, name: "Alice" });
type UserType = ReturnType<typeof getUser>;
 
// Parameters: get the parameter types of a function
type Params = Parameters<typeof getUser>;
 
// NonNullable: exclude null and undefined
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string

Tips

Preserve Literal Types with as const

const COLORS = ["red", "green", "blue"] as const;
type Color = (typeof COLORS)[number]; // "red" | "green" | "blue"

Prefer Type Guards Over Type Assertions

// Type assertion (unsafe)
const value = someValue as string;
 
// Type guard (safe)
const isString = (value: unknown): value is string => typeof value === "string";
 
if (isString(value)) {
  console.log(value.toUpperCase()); // treated as string here
}

Optional Chaining and Nullish Coalescing

const name = user?.profile?.name ?? "Anonymous";
const count = data?.items?.length ?? 0;