⚛️ React Cheat Sheet

Modern React with hooks, TypeScript, context, performance and patterns.

🔍
🧩 Components
Functional component
interface Props {
  name: string;
  count?: number;
  onClick: () => void;
}

const MyButton: React.FC<Props> = ({ name, count = 0, onClick }) => {
  return (
    <button onClick={onClick}>
      {name} ({count})
    </button>
  );
};
JSX rules
// Must have one root element (or Fragment)
return (
  <>
    <h1>Title</h1>
    <p>Body</p>
  </>
);

// Expressions inside {}
<div>{isLoading ? <Spinner /> : <Content />}</div>
<div>{items.length > 0 && <List />}</div>

// className, htmlFor (not class, for)
<input className="input" htmlFor="label" />
Children & slots
const Card = ({ children, header }: {
  children: React.ReactNode;
  header?: React.ReactNode;
}) => (
  <div className="card">
    {header && <div className="header">{header}</div>}
    <div className="body">{children}</div>
  </div>
);

<Card header={<h2>Title</h2>}>
  <p>Content</p>
</Card>
🪝 Core Hooks
useState
const [count, setCount] = useState(0);
const [user, setUser]   = useState<User | null>(null);

// Functional update — safe when next value depends on prev
setCount(prev => prev + 1);

// Object state — must spread to keep other fields
setUser(prev => ({ ...prev!, name: "new" }));
useRef
// DOM ref
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current?.focus();

// Mutable value (doesn't trigger re-render)
const timerRef = useRef<ReturnType<typeof setTimeout>>();
timerRef.current = setTimeout(fn, 1000);
clearTimeout(timerRef.current);
useCallback & useMemo
// useCallback — memoize function reference
const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);

// useMemo — memoize expensive computation
const sorted = useMemo(() =>
  [...items].sort((a,b) => a.name.localeCompare(b.name))
, [items]);
⚡ useEffect
Effect patterns
// Run on mount
useEffect(() => { fetchData(); }, []);

// Run on dependency change
useEffect(() => {
  fetchUser(userId);
}, [userId]);

// Cleanup
useEffect(() => {
  const sub = subscribe(topic, handler);
  return () => sub.unsubscribe();  // cleanup on unmount
}, [topic]);

// Async inside useEffect
useEffect(() => {
  let cancelled = false;
  (async () => {
    const data = await fetchData();
    if (!cancelled) setData(data);
  })();
  return () => { cancelled = true; };
}, []);
useLayoutEffect
// Fires synchronously after DOM mutations
// Use for measuring DOM before paint
useLayoutEffect(() => {
  const rect = ref.current?.getBoundingClientRect();
  setHeight(rect?.height ?? 0);
}, []);
🌐 Context
Create & provide
interface ThemeCtx { theme: string; toggle: () => void; }

const ThemeContext = createContext<ThemeCtx | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState("dark");
  const toggle = useCallback(() =>
    setTheme(t => t === "dark" ? "light" : "dark"), []);
  return (
    <ThemeContext.Provider value={{ theme, toggle }}>
      {children}
    </ThemeContext.Provider>
  );
}
Consume with custom hook
export function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error("useTheme must be inside ThemeProvider");
  return ctx;
}

// Usage
const { theme, toggle } = useTheme();
🔄 useReducer
Reducer pattern
type State  = { count: number; status: "idle" | "loading" };
type Action =
  | { type: "increment" }
  | { type: "setLoading"; payload: boolean };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "increment":
      return { ...state, count: state.count + 1 };
    case "setLoading":
      return { ...state, status: action.payload ? "loading" : "idle" };
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0, status: "idle" });
dispatch({ type: "increment" });
🛠️ Custom Hooks
useFetch
function useFetch<T>(url: string) {
  const [data, setData]   = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(r => r.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);

  return { data, error, loading };
}
useLocalStorage
function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initial;
    } catch { return initial; }
  });
  const set = useCallback((val: T) => {
    setValue(val);
    localStorage.setItem(key, JSON.stringify(val));
  }, [key]);
  return [value, set] as const;
}
⚡ Memo & Performance
React.memo
// Skip re-render if props haven't changed
const ExpensiveItem = React.memo(({ id, name }: Props) => {
  return <div>{name}</div>;
});

// Custom comparator
const Item = React.memo(Component, (prev, next) =>
  prev.id === next.id);
Lazy loading
const HeavyPage = lazy(() => import("./HeavyPage"));

<Suspense fallback={<Spinner />}>
  <HeavyPage />
</Suspense>
📝 Forms
Controlled input
const [value, setValue] = useState("");

<input
  value={value}
  onChange={e => setValue(e.target.value)}
  onKeyDown={e => e.key === "Enter" && submit()}
/>
Form submit
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  const form = new FormData(e.currentTarget);
  const name = form.get("name") as string;
  submit(name);
};

<form onSubmit={handleSubmit}>
  <input name="name" required />
  <button type="submit">Send</button>
</form>
📋 Lists & Keys
Render list
// Always use stable, unique key
{items.map(item => (
  <ListItem key={item.id} item={item} />
))}

// Conditional rendering
{isAdmin && <AdminPanel />}
{status === "loading" ? <Spinner /> : <Content />}
🏷️ TypeScript with React
Event types
onClick:    (e: React.MouseEvent<HTMLButtonElement>) => void
onChange:   (e: React.ChangeEvent<HTMLInputElement>) => void
onSubmit:   (e: React.FormEvent<HTMLFormElement>) => void
onKeyDown:  (e: React.KeyboardEvent<HTMLInputElement>) => void
Generic component
interface SelectProps<T> {
  items: T[];
  value: T;
  getLabel: (item: T) => string;
  onChange:  (item: T) => void;
}

function Select<T>({ items, value, getLabel, onChange }: SelectProps<T>) {
  return (
    <ul>
      {items.map((item, i) => (
        <li key={i} onClick={() => onChange(item)}>
          {getLabel(item)}
        </li>
      ))}
    </ul>
  );
}
🏗️ Patterns
Compound components
const Tabs = ({ children }: { children: React.ReactNode }) => (
  <div className="tabs">{children}</div>
);
Tabs.Tab  = ({ label }: { label: string }) => <div>{label}</div>;
Tabs.Body = ({ children }: { children: React.ReactNode }) => <div>{children}</div>;

<Tabs>
  <Tabs.Tab label="One" />
  <Tabs.Body>Content</Tabs.Body>
</Tabs>
forwardRef
const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ label, ...props }, ref) => (
    <label>
      {label}
      <input ref={ref} {...props} />
    </label>
  )
);

const ref = useRef<HTMLInputElement>(null);
<Input label="Name" ref={ref} />