Introduction to ReactJS
Beginner · 5 min read
React is a JavaScript library for building user interfaces, developed by Meta. It uses a component-based architecture and a virtual DOM to efficiently update the browser. React is the most popular frontend library in the world.
Key Concepts
- Components — reusable, self-contained UI pieces
- JSX — HTML-like syntax inside JavaScript
- Virtual DOM — React reconciles changes and batches DOM updates
- Props — data passed from parent to child (read-only)
- State — data that lives inside a component and causes re-renders
- Hooks — functions that let you use state & lifecycle in function components
React Ecosystem
- Vite — fast development build tool (preferred)
- React Router — client-side routing
- Redux Toolkit / Zustand — global state management
- TanStack Query (React Query) — data fetching & caching
- Tailwind CSS / CSS Modules — styling
- Next.js — full-stack React framework with SSR
Project Setup with Vite
Beginner · 5 min read
# Create React app with Vite (fast, modern)
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev # starts dev server at http://localhost:5173
# Project structure
my-app/
├── src/
│ ├── main.jsx # entry point
│ ├── App.jsx # root component
│ ├── components/
│ └── assets/
├── index.html
├── vite.config.js
└── package.json
// src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root'))
.render(<App />)
JSX & Rendering
Beginner · 8 min read
JSX is a syntax extension that looks like HTML but compiles to React.createElement() calls. It is not HTML — there are important differences.
// JSX basics
const element = <h1 className="title">Hello World</h1>;
// Expressions in JSX — use curly braces {}
const name = 'Aftab';
const el = <p>Hello, {name}! Today is {new Date().toDateString()}.</p>;
// JSX differences from HTML
// className instead of class
<div className="container"></div>
// htmlFor instead of for (on labels)
<label htmlFor="email">Email</label>
// Inline styles are objects
<div style={{ color: 'red', fontSize: '16px' }}></div>
// Must have a single root element (or Fragment)
return (
<> {/* React.Fragment shorthand */}
<h1>Title</h1>
<p>Body</p>
</>
);
// Self-closing tags must close
<img src="photo.jpg" alt="photo" />
<input type="text" />
<br />
Components & Props
Beginner · 10 min read
// Function component (modern React — preferred)
function UserCard({ name, role, avatar }) {
return (
<div className="card">
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{role}</p>
</div>
);
}
// Using the component
function App() {
return (
<UserCard
name="Aftab"
role="Full Stack Dev"
avatar="/images/aftab.jpg"
/>
);
}
// Props with defaults
function Button({ label, variant = 'primary', onClick, disabled = false }) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{label}
</button>
);
}
// children prop
function Card({ title, children }) {
return (
<div className="card">
<h3>{title}</h3>
<div>{children}</div>
</div>
);
}
// Usage:
<Card title="Profile">
<UserCard name="Aftab" />
</Card>
State — useState
Beginner · 10 min read
import { useState } from 'react'
function Counter() {
// [currentValue, setter]
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// Functional update (safe when new state depends on previous)
setCount(prev => prev + 1);
// Object state
const [user, setUser] = useState({ name: '', email: '' });
// Always create a new object (don't mutate)
setUser(prev => ({ ...prev, name: 'Aftab' }));
// Array state
const [items, setItems] = useState([]);
setItems(prev => [...prev, newItem]); // add
setItems(prev => prev.filter(i => i.id !== id)); // remove
setItems(prev => prev.map(i => // update
i.id === id ? { ...i, done: true } : i
));
⚠️ Never mutate state directly — always use the setter. React compares references; if you mutate and pass the same object, React won't re-render.
Event Handling
Beginner · 7 min read
function Form() {
const [value, setValue] = useState('');
// e is SyntheticEvent — React's cross-browser wrapper
const handleChange = (e) => setValue(e.target.value);
const handleSubmit = (e) => {
e.preventDefault(); // prevent page reload
console.log('Submitted:', value);
};
return (
<form onSubmit={handleSubmit}>
<input
value={value}
onChange={handleChange}
onKeyDown={(e) => e.key === 'Enter' && handleSubmit(e)}
/>
<button type="submit">Submit</button>
</form>
);
}
// Passing extra args to handler
<button onClick={(e) => handleDelete(item.id, e)}>Delete</button>
Conditional Rendering
Beginner · 7 min read
function StatusBanner({ isLoggedIn, isLoading, error }) {
// 1. if/else early return
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage msg={error.message} />;
return (
<div>
{/* 2. Ternary */}
{isLoggedIn
? <p>Welcome back!</p>
: <p>Please log in.</p>
}
{/* 3. Short-circuit && (render only if truthy) */}
{isLoggedIn && <Dashboard />}
{/* 4. Nullish coalescing */}
{user?.name ?? 'Guest'}
</div>
);
}
⚠️ Don't use 0 && <Component /> — if the left side is 0 (falsy but not false), React renders the literal 0 on screen. Use count > 0 && ... instead.
Lists & Keys
Beginner · 7 min read
function ProductList({ products }) {
return (
<ul>
{products.map(product => (
<li key={product.id}> {/* key must be unique & stable */}
<span>{product.name}</span>
<span>₹{product.price}</span>
</li>
))}
</ul>
);
}
// With index (only OK if list never reorders or deletes)
{items.map((item, index) => <li key={index}>{item}</li>)}
// Nested lists
{categories.map(cat => (
<div key={cat.id}>
<h3>{cat.name}</h3>
<ul>
{cat.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}
useEffect
Intermediate · 10 min read
useEffect runs side effects after rendering — data fetching, subscriptions, DOM manipulation, timers.
import { useState, useEffect } from 'react'
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// runs after every render where userId changed
let cancelled = false; // prevent stale updates
const load = async () => {
setLoading(true);
const data = await fetchUser(userId);
if (!cancelled) { setUser(data); setLoading(false); }
};
load();
// Cleanup — runs before next effect or unmount
return () => { cancelled = true; };
}, [userId]); // dependency array: re-run when userId changes
if (loading) return <Spinner />;
return <div>{user.name}</div>;
}
// Dependency array patterns
useEffect(() => { ... }); // runs after EVERY render
useEffect(() => { ... }, []); // runs ONCE after mount
useEffect(() => { ... }, [id]); // runs when id changes
// Example: subscribe/unsubscribe
useEffect(() => {
const sub = eventBus.subscribe('update', handleUpdate);
return () => sub.unsubscribe();
}, []);
Context & useContext
Intermediate · 10 min read
Context solves prop drilling — passing props through many layers. It provides a way to share values across any component tree level.
import { createContext, useContext, useState } from 'react'
// 1. Create context
const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {}
});
// 2. Provider — wraps the component tree
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () =>
setTheme(prev => prev === 'light' ? 'dark' : 'light');
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. Custom hook for clean usage
function useTheme() {
return useContext(ThemeContext);
}
// 4. Consumer — any nested component
function NavBar() {
const { theme, toggleTheme } = useTheme();
return (
<nav className={theme}>
<button onClick={toggleTheme}>Toggle Theme</button>
</nav>
);
}
// Root
function App() {
return (
<ThemeProvider>
<NavBar />
<Main />
</ThemeProvider>
);
}
useRef
Intermediate · 8 min read
useRef returns a mutable object whose .current value persists across renders without causing re-renders. Used for DOM access and storing mutable values.
import { useRef, useEffect } from 'react'
// 1. DOM access
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => { inputRef.current?.focus(); }, []);
return <input ref={inputRef} placeholder="Auto-focused!" />;
}
// 2. Store mutable value without re-render
function StopWatch() {
const [time, setTime] = useState(0);
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => setTime(t => t + 1), 1000);
};
const stop = () => clearInterval(intervalRef.current);
return (
<div>
<p>{time}s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
// 3. Store previous value
function usePrevious(value) {
const ref = useRef();
useEffect(() => { ref.current = value; });
return ref.current;
}
useMemo & useCallback
Advanced · 10 min read
import { useMemo, useCallback } from 'react'
// useMemo — memoize expensive computed value
function ProductList({ products, search }) {
const filtered = useMemo(
() => products.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
),
[products, search] // recompute only when these change
);
return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
// useCallback — memoize function reference (for child props)
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback: new function on every render → Child always re-renders
const handleClick = useCallback((id) => {
deleteItem(id);
}, []); // stable reference unless deps change
return <Child onDelete={handleClick} />;
}
// React.memo — prevents re-render if props unchanged
const Child = React.memo(function({ onDelete }) {
console.log('Child rendered');
return <button onClick={() => onDelete(1)}>Delete</button>;
});
💡 Don't over-optimize. Only add useMemo / useCallback when you have a proven performance problem. Premature memoization adds complexity without benefit.
Custom Hooks
Intermediate · 8 min read
Custom hooks extract reusable stateful logic from components. A custom hook is any function starting with use that calls other hooks.
// useFetch — reusable data-fetching hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true); setError(null);
fetch(url)
.then(r => r.json())
.then(d => !cancelled && (setData(d), setLoading(false)))
.catch(e => !cancelled && (setError(e), setLoading(false)));
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// Usage
function Users() {
const { data, loading, error } = useFetch('/api/users');
if (loading) return <Spinner />;
if (error) return <p>Error: {error.message}</p>;
return <UserList users={data} />;
}
// useLocalStorage — persisted state
function useLocalStorage(key, initial) {
const [value, setValue] = useState(
() => JSON.parse(localStorage.getItem(key)) ?? initial
);
const set = (v) => {
setValue(v);
localStorage.setItem(key, JSON.stringify(v));
};
return [value, set];
}
// useDebounce
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const t = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(t);
}, [value, delay]);
return debounced;
}
React Router v6
Intermediate · 10 min read
npm install react-router-dom
// main.jsx
import { BrowserRouter } from 'react-router-dom'
ReactDOM.createRoot(root).render(<BrowserRouter><App/></BrowserRouter>)
// App.jsx — route definitions
import { Routes, Route, Navigate } from 'react-router-dom'
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="/dashboard" element={
isLoggedIn ? <Dashboard/> : <Navigate to="/login" replace/>
} />
<Route path="*" element={<NotFound />} />
</Routes>
);
}
// Navigation hooks
import { useNavigate, useParams, useSearchParams, useLocation } from 'react-router-dom'
const navigate = useNavigate();
navigate('/dashboard');
navigate(-1); // go back
const { id } = useParams(); // /users/:id → id='42'
const [params, setParams] = useSearchParams();
params.get('page'); // ?page=2
// Link component (no page reload)
import { Link, NavLink } from 'react-router-dom'
<Link to="/about">About</Link>
<NavLink to="/home" className={({isActive}) => isActive ? 'active' : ''}>Home</NavLink>
API Calls & Data Fetching
Intermediate · 10 min read
// Basic pattern with useEffect
function PostList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/posts')
.then(r => r.json())
.then(data => { setPosts(data); setLoading(false); });
}, []);
return loading ? <Spinner/> : <List items={posts}/>;
}
// TanStack Query (React Query) — recommended for production
npm install @tanstack/react-query
import { useQuery, useMutation, QueryClient, QueryClientProvider }
from '@tanstack/react-query'
const qc = new QueryClient();
// Wrap app: <QueryClientProvider client={qc}>...</QueryClientProvider>
// useQuery — fetch and cache
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
staleTime: 1000 * 60 // 1 min cache
});
if (isLoading) return <Spinner/>;
return <UserList users={data}/>;
}
// useMutation — create/update/delete
const mutation = useMutation({
mutationFn: (newUser) => fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser)
}),
onSuccess: () => qc.invalidateQueries({ queryKey: ['users'] })
});
Redux Toolkit
Advanced · 12 min read
npm install @reduxjs/toolkit react-redux
// store/cartSlice.js
import { createSlice } from '@reduxjs/toolkit'
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [], total: 0 },
reducers: {
addItem: (state, action) => {
state.items.push(action.payload); // Immer allows mutation!
state.total += action.payload.price;
},
removeItem: (state, action) => {
const idx = state.items.findIndex(i => i.id === action.payload);
if (idx !== -1) state.items.splice(idx, 1);
},
clearCart: (state) => { state.items = []; state.total = 0; }
}
});
export const { addItem, removeItem, clearCart } = cartSlice.actions;
export default cartSlice.reducer;
// store/index.js
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: { cart: cartReducer }
});
// main.jsx
import { Provider } from 'react-redux'
<Provider store={store}><App/></Provider>
// Component — read & dispatch
import { useSelector, useDispatch } from 'react-redux'
function Cart() {
const { items, total } = useSelector(state => state.cart);
const dispatch = useDispatch();
return (
<div>
<p>Items: {items.length} | Total: ₹{total}</p>
<button onClick={() => dispatch(clearCart())}>Clear</button>
</div>
);
}
Component Patterns
Advanced · 10 min read
// 1. Compound Components
function Tabs({ children, defaultTab }) {
const [active, setActive] = useState(defaultTab);
return <TabContext.Provider value={{ active, setActive }}>{children}</TabContext.Provider>;
}
Tabs.Tab = function({ id, label }) { /* ... */ };
Tabs.Panel = function({ id, children }) { /* ... */ };
// Usage:
<Tabs defaultTab="a">
<Tabs.Tab id="a" label="Tab A"/>
<Tabs.Panel id="a">Content A</Tabs.Panel>
</Tabs>
// 2. 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)}
</div>
);
}
<MouseTracker render={({x, y}) => <p>{x},{y}</p>} />
// 3. Higher-Order Component (HOC)
function withAuth(Component) {
return function AuthenticatedComponent(props) {
const { isLoggedIn } = useAuth();
return isLoggedIn ? <Component {...props} /> : <Redirect to="/login"/>;
};
}
const ProtectedDashboard = withAuth(Dashboard);
Error Boundaries
Advanced · 7 min read
Error boundaries catch JavaScript errors anywhere in their child tree, log them, and display a fallback UI. They must be class components (hooks don't support this yet).
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, info) {
// Log to error tracking (Sentry, etc.)
logError(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong</h2>
<button onClick={() => this.setState({ hasError: false })}>
Retry
</button>
</div>
);
}
return this.props.children;
}
}
// Wrap risky components
<ErrorBoundary>
<UserDashboard/>
</ErrorBoundary>
// react-error-boundary library (simpler alternative)
npm install react-error-boundary
import { ErrorBoundary } from 'react-error-boundary'
<ErrorBoundary FallbackComponent={ErrorFallback}>
<App/>
</ErrorBoundary>