React Quick Reference
Components, JSX, hooks, state management, event handling, forms, context API and common patterns.
Component Basics
// Function component (recommended)
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Arrow function component
const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;
// With TypeScript
interface GreetingProps {
name: string;
age?: number;
}
const Greeting: React.FC<GreetingProps> = ({ name, age }) => (
<div>
<h1>Hello, {name}!</h1>
{age && <p>Age: {age}</p>}
</div>
);
// Component with children
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// Usage
<Card title="Welcome">
<p>This is the card content.</p>
</Card>
// Fragment — return multiple elements without a wrapper
function List() {
return (
<>
<li>Item 1</li>
<li>Item 2</li>
</>
);
}
JSX Syntax
// Expressions in JSX — wrap in curly braces
<p>{user.name}</p>
<p>{2 + 2}</p>
<p>{isLoggedIn ? 'Welcome back' : 'Please log in'}</p>
// className instead of class
<div className="container"></div>
// htmlFor instead of for
<label htmlFor="email">Email</label>
// Style — object with camelCase properties
<div style={{ backgroundColor: '#0d1117', fontSize: '1rem' }}></div>
// Boolean attributes
<input disabled />
<button disabled={isLoading}>Submit</button>
// Spread attributes
const props = { id: 'main', className: 'wrapper', role: 'main' };
<div {...props}></div>
// Dangerously set HTML (use with caution)
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
// Self-closing tags required for void elements
<img src="photo.jpg" alt="Photo" />
<br />
<input type="text" />
Props & State
// Props — read-only data passed from parent to child
function UserCard({ name, email, avatar = '/default.png' }) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);
}
// Destructuring with rest
function Button({ children, variant = 'primary', ...rest }) {
return (
<button className={`btn btn-${variant}`} {...rest}>
{children}
</button>
);
}
// State — mutable data managed within a component
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(prev => prev - 1)}>−</button>
</div>
);
}
// State with objects — always create new references
const [user, setUser] = useState({ name: '', email: '' });
setUser(prev => ({ ...prev, name: 'Alex' }));
// State with arrays
const [items, setItems] = useState([]);
setItems(prev => [...prev, newItem]); // add
setItems(prev => prev.filter(item => item.id !== id)); // remove
setItems(prev => prev.map(item =>
item.id === id ? { ...item, done: true } : item // update
));
// Lifting state up — share state via common ancestor
function Parent() {
const [value, setValue] = useState('');
return (
<>
<InputField value={value} onChange={setValue} />
<Display value={value} />
</>
);
}
Hooks
useState
const [state, setState] = useState(initialValue);
// Lazy initialization — runs only on first render
const [data, setData] = useState(() => expensiveComputation());
useEffect
import { useEffect } from 'react';
// Runs after every render
useEffect(() => {
document.title = `Count: ${count}`;
});
// Runs only on mount (empty dependency array)
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(setData);
return () => controller.abort(); // cleanup on unmount
}, []);
// Runs when dependencies change
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
// Cleanup — return a function to run on unmount or before re-run
useEffect(() => {
const id = setInterval(() => tick(), 1000);
return () => clearInterval(id);
}, []);
useContext
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button style={{ background: theme.primary }}>Click</button>;
}
useRef
import { useRef } from 'react';
// DOM reference
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => inputRef.current?.focus();
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus</button>
</>
);
}
// Mutable value that persists across renders (no re-render on change)
const renderCount = useRef(0);
useEffect(() => { renderCount.current += 1; });
// Previous value pattern
function usePrevious(value) {
const ref = useRef();
useEffect(() => { ref.current = value; });
return ref.current;
}
useMemo & useCallback
import { useMemo, useCallback } from 'react';
// useMemo — memoize expensive computations
const sortedItems = useMemo(() => {
return items.slice().sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
// useCallback — memoize function references (prevents child re-renders)
const handleDelete = useCallback((id) => {
setItems(prev => prev.filter(item => item.id !== id));
}, []);
// useCallback is equivalent to useMemo returning a function
const fn = useMemo(() => () => doSomething(a, b), [a, b]);
const fn = useCallback(() => doSomething(a, b), [a, b]);
useReducer
import { useReducer } from 'react';
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.payload };
case 'reset':
return initialState;
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count} (step: {state.step})</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>−</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
Event Handling
// onClick
<button onClick={() => console.log('clicked')}>Click</button>
// With event object
function handleClick(e) {
e.preventDefault();
console.log('Button clicked');
}
<button onClick={handleClick}>Click</button>
// Passing arguments
<button onClick={() => deleteItem(item.id)}>Delete</button>
// onChange for inputs
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
// onSubmit for forms
<form onSubmit={(e) => {
e.preventDefault();
handleSubmit(formData);
}}>
// Keyboard events
<input onKeyDown={(e) => {
if (e.key === 'Enter') handleSubmit();
}} />
// TypeScript event types
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') submit();
};
Conditional Rendering
// Ternary operator
{isLoggedIn ? <Dashboard /> : <Login />}
// Logical AND — renders when condition is truthy
{isAdmin && <AdminPanel />}
{items.length > 0 && <ItemList items={items} />}
// Logical AND pitfall — 0 renders as "0", use boolean conversion
{!!count && <Badge count={count} />}
{count > 0 && <Badge count={count} />}
// Nullish coalescing for fallbacks
{user?.name ?? 'Anonymous'}
// Early return pattern
function UserProfile({ user }) {
if (!user) return <p>Loading...</p>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// Variable assignment
function StatusIcon({ status }) {
let icon;
if (status === 'success') icon = <CheckIcon />;
else if (status === 'error') icon = <ErrorIcon />;
else icon = <SpinnerIcon />;
return <span className="status">{icon}</span>;
}
// Render nothing
function MaybeVisible({ show, children }) {
if (!show) return null;
return children;
}
Lists & Keys
// Basic list rendering
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// With index as key (only when items have no stable ID and list is static)
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
// Extracting list item components
function TodoItem({ todo, onToggle }) {
return (
<li onClick={() => onToggle(todo.id)}>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
</li>
);
}
function TodoList({ todos, onToggle }) {
return (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} onToggle={onToggle} />
))}
</ul>
);
}
// Filtering and mapping
{users
.filter(u => u.active)
.map(u => <UserCard key={u.id} user={u} />)
}
// Empty state
{items.length === 0
? <p className="empty">No items yet.</p>
: items.map(item => <Item key={item.id} {...item} />)
}
Keys must be stable, unique among siblings, and should not change between renders. Use database IDs or unique identifiers — avoid array indices for dynamic lists where items can be reordered, added or removed.
Forms
// Controlled component
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
login({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit">Log In</button>
</form>
);
}
// Multiple fields with single state object
function ProfileForm() {
const [form, setForm] = useState({ name: '', bio: '', website: '' });
const handleChange = (e) => {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
};
return (
<form>
<input name="name" value={form.name} onChange={handleChange} />
<textarea name="bio" value={form.bio} onChange={handleChange} />
<input name="website" value={form.website} onChange={handleChange} />
</form>
);
}
// Uncontrolled component with useRef
function FileUpload() {
const fileRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
const file = fileRef.current.files[0];
uploadFile(file);
};
return (
<form onSubmit={handleSubmit}>
<input type="file" ref={fileRef} />
<button type="submit">Upload</button>
</form>
);
}
// Select, checkbox, radio
<select value={role} onChange={(e) => setRole(e.target.value)}>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
<option value="viewer">Viewer</option>
</select>
<input type="checkbox" checked={agreed} onChange={(e) => setAgreed(e.target.checked)} />
<input type="radio" name="plan" value="free" checked={plan === 'free'} onChange={(e) => setPlan(e.target.value)} />
Context API
import { createContext, useContext, useState } from 'react';
// 1. Create context with default value
const ThemeContext = createContext({
theme: 'dark',
toggleTheme: () => {}
});
// 2. Provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('dark');
const toggleTheme = () => {
setTheme(prev => (prev === 'dark' ? 'light' : 'dark'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Custom hook for consuming context
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
// 4. Use in components
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header className={`header-${theme}`}>
<button onClick={toggleTheme}>
Switch to {theme === 'dark' ? 'light' : 'dark'}
</button>
</header>
);
}
// 5. Wrap your app
function App() {
return (
<ThemeProvider>
<Header />
<Main />
</ThemeProvider>
);
}
Context is ideal for global data like themes, auth state and locale. For high-frequency updates, consider using state management libraries (Zustand, Jotai) or splitting context into separate providers to minimize re-renders.
Common Patterns
Custom Hooks
// useFetch — reusable data fetching
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
fetch(url, { signal: controller.signal })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') setError(err);
})
.finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// useLocalStorage — persist state to localStorage
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// useDebounce
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
Error Boundaries
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error boundary caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <h2>Something went wrong.</h2>;
}
return this.props.children;
}
}
// Usage
<ErrorBoundary fallback={<p>Failed to load.</p>}>
<UserProfile />
</ErrorBoundary>
Code Splitting
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}