Complete ReactJS References & Tips with Code Examples
Comprehensive ReactJS reference guide covering components, props, state, hooks, routing, events, patterns, and practical tips with code examples and related tutorials.
2026-09-19 · 25 min read
Hello my friend! Welcome to the Complete ReactJS References & Tips guide on dev.rean.me!
React is the most popular JavaScript library for building fast, interactive, and reusable user interfaces. In this guide, we cover all essential React concepts — components, props, state, hooks, events, routing, patterns and best practices — all grouped by category with clear code examples and related tutorial links. Bookmark this page for quick daily reference! ⚛️
1. Project Setup & File Structure
Setup React Project with Vite (Recommended)
# Create new React + Vite project
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev
Recommended Folder Structure
my-app/
├── public/ # Static assets (favicon, images)
├── src/
│ ├── assets/ # Images, fonts imported in code
│ ├── components/ # Reusable shared UI components
│ │ └── Navbar.jsx
│ ├── pages/ # Page-level components
│ │ ├── Home.jsx
│ │ └── About.jsx
│ ├── hooks/ # Custom hooks
│ │ └── useFetch.js
│ ├── context/ # React Context providers
│ ├── utils/ # Utility/helper functions
│ ├── App.jsx # Root component with routing
│ └── main.jsx # Entry point — renders App into DOM
├── index.html
├── package.json
└── vite.config.js
💡 Tip: Keep
components/for small reusable pieces (Button, Card, Modal) andpages/for full page views (HomePage, AboutPage). This separation makes large projects much easier to navigate!
🏷️ Related Posts:
2. JSX Syntax Rules
JSX is the HTML-like syntax used inside React components. It compiles to React.createElement() calls.
| Rule | Correct | Wrong |
|---|---|---|
| Single root element | <> ... </> or <div> | Two sibling elements |
| Self-closing tags | <img /> <input /> | <img> <input> |
| Class attribute | className="" | class="" |
| Inline styles | style={{ color: "red" }} | style="color: red" |
| JS expressions | {variable} {fn()} | Direct JS without {} |
| Event names | onClick onChange | onclick onchange |
| HTML entities | © & | Direct special chars |
| Comments | {/* comment */} | // comment inside JSX |
function UserCard({ name, age, avatarUrl, isAdmin }) {
const fullTitle = isAdmin ? "Admin" : "Member";
return (
// Must have single root — use Fragment <> to avoid extra div!
<>
<img
src={avatarUrl}
alt={`Avatar of ${name}`}
className="avatar"
style={{ borderRadius: "50%", width: 80 }}
/>
<h2 className="card-title">{name}</h2>
<p>Age: {age}</p>
<p>Role: {fullTitle}</p>
{/* Conditional rendering */}
{isAdmin && <span className="badge">⭐ Admin</span>}
</>
);
}
💡 Tip: Use
<>...</>(Fragment shorthand) as your root element instead of<div>to avoid unnecessary DOM wrapper nodes that can break Flexbox and Grid layouts!
🏷️ Related Posts:
3. Components & Props
Component Types
| Type | Syntax | Use When |
|---|---|---|
| Functional (Modern) | function MyComp() {} | Always — standard in React 16.8+ |
| Arrow Functional | const MyComp = () => {} | Same as above, concise syntax |
| Class (Legacy) | class MyComp extends Component | Avoid — only in old codebases |
// ── FUNCTIONAL COMPONENT (standard) ──────────────
function Welcome({ name, role = "Member" }) {
return (
<div className="welcome-card">
<h1>Hello, {name}!</h1>
<p>Role: {role}</p>
</div>
);
}
// ── ARROW FUNCTION COMPONENT ──────────────────────
const WelcomeArrow = ({ name }) => (
<h1>Welcome, {name}!</h1>
);
// ── PASSING PROPS (Parent → Child) ───────────────
function App() {
return (
<div>
<Welcome name="Sok Dara" role="Admin" />
<Welcome name="Keo Bopha" /> {/* role defaults to "Member" */}
</div>
);
}
export default App;
Props Patterns
// ── CHILDREN PROP ─────────────────────────────────
function Card({ title, children }) {
return (
<div className="card">
<h3>{title}</h3>
<div className="card-body">{children}</div>
</div>
);
}
// Usage:
<Card title="My Card">
<p>This content is passed as children!</p>
<button>Click Me</button>
</Card>
// ── SPREAD PROPS (pass all props at once) ─────────
const buttonProps = { type: "submit", disabled: false, className: "btn-primary" };
<button {...buttonProps}>Submit</button>
// ── PROP TYPES PATTERN (runtime type checking) ────
// Install: npm install prop-types
import PropTypes from "prop-types";
Welcome.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
isAdmin: PropTypes.bool,
onLogin: PropTypes.func,
tags: PropTypes.arrayOf(PropTypes.string),
};
Welcome.defaultProps = {
age: 0,
isAdmin: false,
};
💡 Tip: Always destructure props directly in the function parameter
({ name, age })instead of usingprops.name— it's cleaner and documents what the component expects at a glance!
🏷️ Related Posts:
- 📑 02 — Functional & Class Component with Props in ReactJS (with Video)
- 📑 ReactJS Tips & Tricks with Examples (Bad vs Good Code)
4. State Management — useState
State is data stored inside a component that, when changed, causes the component to re-render.
import { useState } from "react";
// ── BASIC COUNTER ─────────────────────────────────
function Counter() {
const [count, setCount] = useState(0); // initial value = 0
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// ── FUNCTIONAL UPDATER (use when new state depends on previous) ──
function SafeCounter() {
const [count, setCount] = useState(0);
// ✅ Safe! Always uses latest count value
const increment = () => setCount(prev => prev + 1);
return <button onClick={increment}>Count: {count}</button>;
}
// ── OBJECT STATE ──────────────────────────────────
function UserProfile() {
const [user, setUser] = useState({ name: "Rean", age: 25 });
// ✅ Spread existing state to avoid overwriting other fields!
const updateName = (newName) =>
setUser(prev => ({ ...prev, name: newName }));
return (
<div>
<p>Name: {user.name} | Age: {user.age}</p>
<input onChange={(e) => updateName(e.target.value)} />
</div>
);
}
// ── ARRAY STATE ───────────────────────────────────
function TodoList() {
const [todos, setTodos] = useState(["Learn React", "Build App"]);
// Add item
const addTodo = (text) =>
setTodos(prev => [...prev, text]);
// Remove item by index
const removeTodo = (index) =>
setTodos(prev => prev.filter((_, i) => i !== index));
// Update item by index
const updateTodo = (index, newText) =>
setTodos(prev =>
prev.map((item, i) => (i === index ? newText : item))
);
return (
<ul>
{todos.map((todo, i) => (
<li key={i}>
{todo}
<button onClick={() => removeTodo(i)}>✕</button>
</li>
))}
</ul>
);
}
💡 Tip: NEVER mutate state directly like
user.name = "Dara"ortodos.push("item")! React detects changes by comparing old and new state references — direct mutation doesn't trigger a re-render!
🏷️ Related Posts:
5. Event Handling
React event names are camelCase and receive a synthetic event object (e).
Common Event Handlers
| Event | Handler Prop | Use Case |
|---|---|---|
| Mouse click | onClick | Buttons, links, cards |
| Input change | onChange | Text inputs, selects, checkboxes |
| Form submit | onSubmit | Form submission |
| Focus / Blur | onFocus / onBlur | Input validation |
| Key press | onKeyDown / onKeyUp | Shortcuts, Enter key |
| Mouse hover | onMouseEnter / onMouseLeave | Tooltips, hover effects |
| Drag events | onDragStart / onDrop | Drag & drop UI |
import { useState } from "react";
function EventExamples() {
const [text, setText] = useState("");
const [submitted, setSubmitted] = useState(null);
// ✅ Pass function reference — NOT invocation!
const handleClick = () => alert("Button clicked!");
// ✅ Pass args using arrow function wrapper
const handleGreet = (name) => alert(`Hello, ${name}!`);
// ✅ onChange — read e.target.value
const handleChange = (e) => setText(e.target.value);
// ✅ Always call e.preventDefault() on form submit!
const handleSubmit = (e) => {
e.preventDefault();
setSubmitted(text);
setText(""); // clear input after submit
};
// ✅ Keyboard shortcut detection
const handleKeyDown = (e) => {
if (e.key === "Enter") handleSubmit(e);
if (e.key === "Escape") setText("");
};
return (
<div>
{/* ❌ Wrong: handleClick() — runs immediately on render! */}
{/* ✅ Correct: handleClick — reference only */}
<button onClick={handleClick}>Click Me</button>
<button onClick={() => handleGreet("Dara")}>Greet Dara</button>
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder="Type something..."
/>
<button type="submit">Submit</button>
</form>
{submitted && <p>✅ Submitted: {submitted}</p>}
</div>
);
}
💡 Tip: Never write
onClick={handleClick()}— the()invokes the function immediately during render, not on click. WriteonClick={handleClick}(reference) oronClick={() => handleClick(args)}(with arguments).
🏷️ Related Posts:
6. React Hooks — Complete Reference
Hooks let functional components use state, lifecycle, context, and more.
Rules of Hooks
- ✅ Only call hooks at the top level — not inside loops, conditions, or nested functions
- ✅ Only call hooks inside React functional components (or custom hooks)
Built-in Hooks Quick Reference
| Hook | Purpose |
|---|---|
useState | Store and update local component state |
useEffect | Run side effects (fetch, timers, subscriptions) |
useContext | Consume a React Context value |
useRef | Mutable ref without re-render; access DOM nodes |
useMemo | Memoize expensive computed values |
useCallback | Memoize function references (stable between renders) |
useReducer | Manage complex state with a reducer function |
useId | Generate unique IDs for accessibility |
useTransition | Mark non-urgent state updates for concurrent mode |
useDeferredValue | Defer updating a value for performance |
import { useState, useEffect, useRef, useMemo, useCallback, useReducer } from "react";
// ── useEffect ─────────────────────────────────────
function UserFetcher({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Runs after every render where userId changes
setLoading(true);
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(r => r.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
if (err.name !== "AbortError") console.error(err);
});
// Cleanup: cancel fetch if component unmounts or userId changes
return () => controller.abort();
}, [userId]); // Dependency array — re-run when userId changes
if (loading) return <p>Loading...</p>;
return <p>User: {user?.name}</p>;
}
// ── useRef ────────────────────────────────────────
function FocusInput() {
const inputRef = useRef(null);
// Focus the input programmatically
const focusInput = () => inputRef.current?.focus();
return (
<>
<input ref={inputRef} type="text" placeholder="Click button to focus" />
<button onClick={focusInput}>Focus Input</button>
</>
);
}
// ── useMemo (memoize expensive computation) ───────
function ExpensiveList({ items, filter }) {
const filteredItems = useMemo(
() => items.filter(item => item.name.includes(filter)),
[items, filter] // Only recomputes when items or filter changes
);
return <ul>{filteredItems.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}
// ── useCallback (stable function reference) ───────
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback, new function reference on every render
// causes Child to re-render unnecessarily
const handleAdd = useCallback(() => {
setCount(prev => prev + 1);
}, []); // Never changes — empty deps
return <Child onAdd={handleAdd} />;
}
// ── useReducer (complex state logic) ─────────────
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 AdvancedCounter() {
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: "setStep", payload: 5 })}>Set Step 5</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
);
}
💡 Tip: Use
useReducerinstead of multipleuseStatehooks when your state has many fields that update together, or when the next state depends on multiple parts of current state. It makes the logic much easier to test!
🏷️ Related Posts:
7. useEffect — Side Effects Reference
useEffect runs after the browser has painted the screen.
| Dependency Array | Runs When |
|---|---|
No array: useEffect(fn) | After every render |
Empty array: useEffect(fn, []) | Once — on mount only (like componentDidMount) |
With deps: useEffect(fn, [a, b]) | On mount + whenever a or b changes |
| Return cleanup function | Cleanup runs on unmount or before next effect |
import { useState, useEffect } from "react";
// ── Common useEffect Patterns ─────────────────────
// 1. Fetch on mount
useEffect(() => {
fetch("/api/data").then(r => r.json()).then(setData);
}, []); // [] = only once on mount
// 2. React to prop/state changes
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]); // Re-run when count changes
// 3. Cleanup (timers, subscriptions, fetch abort)
useEffect(() => {
const intervalId = setInterval(() => {
setTick(t => t + 1);
}, 1000);
return () => clearInterval(intervalId); // Cleanup on unmount!
}, []);
// 4. Event listener on window
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
// 5. Local storage sync
useEffect(() => {
localStorage.setItem("theme", theme);
}, [theme]);
💡 Tip: Always return a cleanup function inside
useEffectwhen you create subscriptions, timers, or fetch requests! Forgetting cleanup is the most common cause of memory leaks in React apps.
8. Context API — Global State
React Context lets you share data across your component tree without passing props at every level (avoids "prop drilling").
import { createContext, useContext, useState } from "react";
// ── 1. Create Context ─────────────────────────────
const ThemeContext = createContext("light"); // default value
// ── 2. Create Provider Component ──────────────────
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () =>
setTheme(prev => (prev === "light" ? "dark" : "light"));
return (
// All children now have access to theme & toggleTheme
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// ── 3. Custom Hook (clean consumer pattern) ───────
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used inside ThemeProvider!");
}
return context;
}
// ── 4. Use in any child component ─────────────────
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header className={`header header--${theme}`}>
<h1>My App</h1>
<button onClick={toggleTheme}>
Switch to {theme === "light" ? "Dark" : "Light"} Mode
</button>
</header>
);
}
// ── 5. Wrap your app ──────────────────────────────
function App() {
return (
<ThemeProvider>
<Header />
{/* All nested components can access theme! */}
</ThemeProvider>
);
}
💡 Tip: Always create a custom hook like
useTheme()oruseAuth()to consume context! This pattern adds error checking (when context is used outside its provider) and hidesuseContextimplementation details from consumers.
9. Custom Hooks
Custom hooks extract reusable stateful logic into standalone functions. They always start with use.
import { useState, useEffect, useCallback } from "react";
// ── useFetch: reusable data fetching ──────────────
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) return;
const controller = new AbortController();
setLoading(true);
setError(null);
fetch(url, { signal: controller.signal })
.then(res => {
if (!res.ok) throw new Error(`HTTP Error ${res.status}`);
return res.json();
})
.then(setData)
.catch(err => {
if (err.name !== "AbortError") setError(err.message);
})
.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 {
return JSON.parse(localStorage.getItem(key)) ?? initialValue;
} catch {
return initialValue;
}
});
const setStoredValue = useCallback((newValue) => {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
}, [key]);
return [value, setStoredValue];
}
// ── useDebounce: debounce rapidly changing values ──
function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// ── useToggle: boolean toggle helper ──────────────
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue(v => !v), []);
return [value, toggle];
}
// ── Usage Examples ─────────────────────────────────
function UsersPage() {
const { data: users, loading, error } = useFetch("/api/users");
const [savedUsers, setSavedUsers] = useLocalStorage("users", []);
const [isMenuOpen, toggleMenu] = useToggle(false);
const [search, setSearch] = useState("");
const debouncedSearch = useDebounce(search, 400);
// Only fetches when debouncedSearch stabilizes
const { data: results } = useFetch(
debouncedSearch ? `/api/search?q=${debouncedSearch}` : null
);
if (loading) return <p>Loading users...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<button onClick={toggleMenu}>{isMenuOpen ? "Close" : "Open"} Menu</button>
<input value={search} onChange={e => setSearch(e.target.value)} />
<ul>{users?.map(u => <li key={u.id}>{u.name}</li>)}</ul>
</div>
);
}
💡 Tip: Custom hooks are just JavaScript functions — they can call other custom hooks, can be tested in isolation, and can be shared across projects as npm packages. Always name them starting with
useso React linting rules apply!
🏷️ Related Posts:
10. Conditional Rendering
function UserDashboard({ user, isLoading, isError }) {
// ── Pattern 1: Early return (guard clause) ────────
if (isLoading) return <p>Loading...</p>;
if (isError) return <p>Something went wrong!</p>;
if (!user) return <p>No user found.</p>;
return (
<div>
{/* ── Pattern 2: && short-circuit ──────────── */}
{user.isAdmin && <span className="badge">Admin</span>}
{/* ── Pattern 3: Ternary ───────────────────── */}
<p>Status: {user.isActive ? "🟢 Active" : "🔴 Inactive"}</p>
{/* ── Pattern 4: Variable ──────────────────── */}
{(() => {
if (user.role === "admin") return <AdminPanel />;
if (user.role === "editor") return <EditorPanel />;
return <ViewerPanel />;
})()}
{/* ── Pattern 5: Nullish coalescing ────────── */}
<p>{user.bio ?? "No bio provided."}</p>
</div>
);
}
💡 Tip: Avoid
&&with numbers!{count && <Component />}renders0when count is 0 instead of nothing. Use{count > 0 && <Component />}or{Boolean(count) && <Component />}instead!
11. List Rendering & Keys
const users = [
{ id: 1, name: "Sok Dara", role: "Dev" },
{ id: 2, name: "Keo Bopha", role: "Designer" },
{ id: 3, name: "Vibol Kiri", role: "PM" },
];
function UserList() {
return (
<ul>
{users.map(user => (
// ✅ key must be unique and stable — use ID, not index!
<li key={user.id}>
<strong>{user.name}</strong> — {user.role}
</li>
))}
</ul>
);
}
// ── Fragment with key (when wrapping multiple elements) ──
function ProductRows({ products }) {
return (
<tbody>
{products.map(product => (
// ✅ Use React.Fragment with key when returning multiple elements
<React.Fragment key={product.id}>
<tr><td>{product.name}</td></tr>
<tr><td>{product.description}</td></tr>
</React.Fragment>
))}
</tbody>
);
}
// ── Filtering + Sorting before render ────────────
function FilteredList({ items, searchTerm }) {
const displayed = items
.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase()))
.sort((a, b) => a.name.localeCompare(b.name));
if (displayed.length === 0) return <p>No results found.</p>;
return (
<ul>
{displayed.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
💡 Tip: Never use array index as
keywhen list items can be reordered, filtered, or deleted — React uses keys to track identity. Using index causes state, animations, and focus to misbehave! Always use a stable unique ID from your data.
🏷️ Related Posts:
12. Forms — Controlled & Uncontrolled
import { useState, useRef } from "react";
// ── CONTROLLED FORM (React manages value) ─────────
function ControlledForm() {
const [form, setForm] = useState({
name: "",
email: "",
role: "member",
agree: false,
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setForm(prev => ({
...prev,
[name]: type === "checkbox" ? checked : value,
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("Form submitted:", form);
};
const isValid = form.name.trim() && form.email.includes("@") && form.agree;
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={form.name}
onChange={handleChange}
placeholder="Full Name"
required
/>
<input
name="email"
type="email"
value={form.email}
onChange={handleChange}
placeholder="Email"
/>
<select name="role" value={form.role} onChange={handleChange}>
<option value="member">Member</option>
<option value="editor">Editor</option>
<option value="admin">Admin</option>
</select>
<label>
<input
name="agree"
type="checkbox"
checked={form.agree}
onChange={handleChange}
/>
I agree to terms
</label>
<button type="submit" disabled={!isValid}>Register</button>
</form>
);
}
// ── UNCONTROLLED FORM (DOM manages value) ─────────
function UncontrolledForm() {
const nameRef = useRef(null);
const emailRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log({
name: nameRef.current.value,
email: emailRef.current.value,
});
};
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} name="name" placeholder="Full Name" />
<input ref={emailRef} name="email" type="email" placeholder="Email" />
<button type="submit">Submit</button>
</form>
);
}
💡 Tip: Use the
[name]computed property key pattern inhandleChangeto handle ALL input fields with a single handler function instead of writing separatesetName,setEmail,setRolehandlers!
🏷️ Related Posts:
13. React Router — Navigation Reference
npm install react-router-dom
Core Components
| Component | Purpose |
|---|---|
<BrowserRouter> | Wraps entire app — enables client-side routing |
<Routes> | Container for all <Route> definitions |
<Route path="" element={}> | Maps URL path to a component |
<Link to=""> | Navigation link (no page reload) |
<NavLink to=""> | Link with active class support |
<Navigate to=""> | Programmatic redirect inside JSX |
<Outlet> | Renders nested child routes |
Hooks
| Hook | Returns | Use Case |
|---|---|---|
useNavigate() | navigate function | Programmatic navigation |
useParams() | URL params object | Read :id from URL |
useSearchParams() | [params, setParams] | Read/write query strings |
useLocation() | Location object | Current URL, state |
import {
BrowserRouter, Routes, Route, Link, NavLink,
Navigate, Outlet, useNavigate, useParams
} from "react-router-dom";
// ── App Router Setup ──────────────────────────────
function App() {
return (
<BrowserRouter>
<Navbar />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
{/* Dynamic route with param */}
<Route path="/users/:id" element={<UserDetailPage />} />
{/* Nested routes */}
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="stats" element={<StatsPage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
{/* Redirect */}
<Route path="/old-page" element={<Navigate to="/new-page" replace />} />
{/* 404 catch-all */}
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
}
// ── Navbar with NavLink ───────────────────────────
function Navbar() {
return (
<nav>
{/* NavLink automatically adds "active" class when route matches */}
<NavLink to="/" end>Home</NavLink>
<NavLink to="/about">About</NavLink>
<NavLink to="/dashboard">Dashboard</NavLink>
</nav>
);
}
// ── Dynamic route — read URL params ───────────────
function UserDetailPage() {
const { id } = useParams(); // /users/42 → id = "42"
const { data: user, loading } = useFetch(`/api/users/${id}`);
if (loading) return <p>Loading...</p>;
return <h1>User: {user?.name}</h1>;
}
// ── Nested layout — render children in <Outlet> ───
function DashboardLayout() {
return (
<div className="dashboard">
<aside>
<Link to="/dashboard">Home</Link>
<Link to="/dashboard/stats">Stats</Link>
<Link to="/dashboard/settings">Settings</Link>
</aside>
<main>
<Outlet /> {/* Child routes render here */}
</main>
</div>
);
}
// ── Programmatic navigation ────────────────────────
function LoginPage() {
const navigate = useNavigate();
const handleLogin = async () => {
await loginUser();
navigate("/dashboard", { replace: true }); // replace history entry
};
return <button onClick={handleLogin}>Login</button>;
}
💡 Tip: Never use
<a href="">for internal links in React! It causes a full page reload and resets all React state. Always use<Link to="">fromreact-router-domfor internal navigation!
🏷️ Related Posts:
14. Performance Optimization
import { memo, useMemo, useCallback, lazy, Suspense } from "react";
// ── React.memo (skip re-render if props unchanged) ──
const UserCard = memo(function UserCard({ name, role }) {
console.log("Rendering UserCard:", name);
return <div><h3>{name}</h3><p>{role}</p></div>;
});
// ── Lazy loading components (code splitting) ───────
const HeavyChart = lazy(() => import("./HeavyChart"));
const AdminPanel = lazy(() => import("./AdminPanel"));
function Dashboard({ isAdmin }) {
return (
<Suspense fallback={<div>Loading component...</div>}>
<HeavyChart />
{isAdmin && <AdminPanel />}
</Suspense>
);
}
// ── useMemo — memoize computed value ──────────────
function ProductList({ products, discount }) {
// Only recalculates when products or discount changes
const discountedProducts = useMemo(() =>
products.map(p => ({
...p,
finalPrice: p.price * (1 - discount / 100),
})),
[products, discount]
);
return <ul>{discountedProducts.map(p => (
<li key={p.id}>{p.name}: ${p.finalPrice.toFixed(2)}</li>
))}</ul>;
}
// ── useCallback — stable function reference ────────
function Parent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
// Without useCallback, new reference on every render → Child re-renders!
const addItem = useCallback((item) => {
setItems(prev => [...prev, item]);
}, []); // Stable reference — never changes
return (
<>
<button onClick={() => setCount(c => c + 1)}>Re-render Parent ({count})</button>
<MemoizedChildList onAdd={addItem} />
</>
);
}
💡 Tip: Don't over-optimize! Apply
memo,useMemo,useCallbackonly when you measure a real performance problem with React DevTools Profiler. Premature optimization adds complexity without benefit!
🏷️ Related Posts:
15. Component Patterns
// ── COMPOUND COMPONENTS ───────────────────────────
function Tabs({ children }) {
const [active, setActive] = useState(0);
return (
<div className="tabs">
<div className="tab-list">
{React.Children.map(children, (child, i) =>
React.cloneElement(child, {
isActive: i === active,
onClick: () => setActive(i),
})
)}
</div>
</div>
);
}
// ── RENDER PROPS ──────────────────────────────────
function MouseTracker({ render }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>
{render(pos)} {/* Caller controls what to render */}
</div>
);
}
// Usage: <MouseTracker render={({ x, y }) => <p>X:{x} Y:{y}</p>} />
// ── HIGHER-ORDER COMPONENT (HOC) ──────────────────
function withAuth(WrappedComponent) {
return function AuthGuard(props) {
const isLoggedIn = useAuth().isLoggedIn;
if (!isLoggedIn) return <Navigate to="/login" />;
return <WrappedComponent {...props} />;
};
}
const ProtectedDashboard = withAuth(Dashboard);
// ── CONTROLLED vs UNCONTROLLED COMPONENTS ─────────
// Controlled: parent owns state via value + onChange
function ControlledInput({ value, onChange }) {
return <input value={value} onChange={e => onChange(e.target.value)} />;
}
// Uncontrolled: internal state, exposed via ref/callback
function UncontrolledInput({ defaultValue, onBlurSubmit }) {
const [val, setVal] = useState(defaultValue);
return (
<input
value={val}
onChange={e => setVal(e.target.value)}
onBlur={() => onBlurSubmit(val)}
/>
);
}
16. React Best Practices Summary
| ✅ Do | ❌ Avoid |
|---|---|
| Use Functional Components with Hooks | Using Class Components (legacy) |
Use <>...</> Fragment as root wrapper | Wrapping with unnecessary <div> |
Use key={item.id} stable unique ID | Using key={index} in lists |
Use functional updater setCount(prev => prev + 1) | Direct setCount(count + 1) in async |
Call e.preventDefault() on form submit | Letting forms reload the page |
Use <Link to=""> for internal navigation | Using <a href=""> inside React Router apps |
Use useCallback for props passed to memoized children | Recreating functions inline in render |
| Keep components small & single-purpose | Giant 500+ line components |
| Use Context or state management for global data | Deep prop drilling (5+ levels) |
| Lift state up to the nearest common ancestor | Duplicating state in sibling components |
Return cleanup in useEffect | Forgetting to cancel subscriptions/timers |
Use structuredClone() for deep copy | JSON.parse(JSON.stringify()) |
| TypeScript for type safety | any type everywhere in TypeScript |
Summary Tag Cloud Directory
Happy coding and building awesome React applications! ⚛️🚀