⚛️ 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} />