What You'll Learn
Introduction to React
React is a JavaScript library for building user interfaces, developed by Facebook. It allows you to create reusable UI components and efficiently update the DOM through its Virtual DOM mechanism.
Why React?
Component-Based: Build encapsulated components that manage their own state. Used by Facebook, Instagram, Netflix, Airbnb, and many more!
- Components: Reusable building blocks of your UI
- JSX: JavaScript XML - write HTML-like syntax in JavaScript
- Virtual DOM: Efficient DOM updates through diffing algorithm
- One-Way Data Flow: Data flows from parent to child via props
Setup & Installation
Install Node.js
Make sure you have Node.js 18+ installed. Check with node --version
Create React App (Vite - Recommended)
Vite is the modern, faster alternative to Create React App
Open http://localhost:5173/ in your browser - You'll see the React welcome page!
React App Created!
Your React development environment is ready!
JSX Syntax
JSX (JavaScript XML) is a syntax extension that lets you write HTML-like code in JavaScript. It's not required for React, but it makes the code more readable and easier to write.
Basic JSX
// JSX looks like HTML but it's JavaScript! function App() { const name = "Om Pandey"; const age = 25; return ( <div className="container"> {/* This is a JSX comment */} <h1>Hello, {name}!</h1> <p>You are {age} years old.</p> <p>2 + 2 = {2 + 2}</p> </div> ); } export default App;
JSX Rules
// Rule 1: Return single root element // ❌ Wrong return ( <h1>Title</h1> <p>Paragraph</p> ); // ✅ Correct - wrap in a parent or use Fragment return ( <> <h1>Title</h1> <p>Paragraph</p> </> ); // Rule 2: Use className instead of class <div className="my-class">Content</div> // Rule 3: Use camelCase for attributes <button onClick={handleClick}>Click</button> <label htmlFor="name">Name</label> <input tabIndex={1} /> // Rule 4: Close all tags <img src="photo.jpg" alt="Photo" /> <br /> <input type="text" /> // Rule 5: Use curly braces for JavaScript <p style={{ color: 'red', fontSize: '20px' }}>Styled</p>
Important!
JSX is transpiled to JavaScript by Babel. <div> becomes React.createElement('div')
Components
Components are the building blocks of React applications. They are reusable, independent pieces of UI that can have their own logic and styling.
Function Components
// Function Component (Recommended) function Header() { return ( <header> <h1>My Website</h1> <nav> <a href="/">Home</a> <a href="/about">About</a> </nav> </header> ); } export default Header;
// Arrow Function Component const Card = () => { return ( <div className="card"> <h2>Card Title</h2> <p>Card content goes here...</p> </div> ); }; export default Card;
Using Components
import Header from './components/Header'; import Card from './components/Card'; import Footer from './components/Footer'; function App() { return ( <div className="app"> <Header /> <main> <Card /> <Card /> <Card /> </main> <Footer /> </div> ); } export default App;
Component Naming
Components must start with an uppercase letter. <Header /> is a component, <header> is HTML.
Props & Data Flow
Props (properties) are how you pass data from a parent component to a child component. They are read-only and help make components reusable.
Passing Props
import UserCard from './components/UserCard'; function App() { return ( <div> {/* Passing props to child component */} <UserCard name="Om Pandey" age={25} isAdmin={true} skills={['React', 'JavaScript', 'Python']} /> <UserCard name="John Doe" age={30} isAdmin={false} skills={['HTML', 'CSS']} /> </div> ); }
Receiving Props
// Method 1: Props object function UserCard(props) { return ( <div className="user-card"> <h2>{props.name}</h2> <p>Age: {props.age}</p> <p>Role: {props.isAdmin ? 'Admin' : 'User'}</p> </div> ); } // Method 2: Destructuring (Recommended) function UserCard({ name, age, isAdmin, skills }) { return ( <div className="user-card"> <h2>{name}</h2> <p>Age: {age}</p> <p>Role: {isAdmin ? 'Admin' : 'User'}</p> <ul> {skills.map((skill, index) => ( <li key={index}>{skill}</li> ))} </ul> </div> ); } export default UserCard;
Default Props
// Default values using destructuring function Button({ text = "Click Me", color = "blue", size = "medium", onClick }) { return ( <button className={`btn btn-${color} btn-${size}`} onClick={onClick} > {text} </button> ); } export default Button; // Usage: // <Button /> - Uses all defaults // <Button text="Submit" color="green" />
Children Props
// Card component that accepts children function Card({ title, children }) { return ( <div className="card"> <h2>{title}</h2> <div className="card-body"> {children} </div> </div> ); } // Usage in App.jsx function App() { return ( <Card title="Welcome"> <p>This is the card content.</p> <button>Learn More</button> </Card> ); }
State Management
State is data that changes over time in your component. When state changes, React re-renders the component to reflect the new data.
useState Hook
import { useState } from 'react'; function Counter() { // Declare state: [value, setterFunction] = useState(initialValue) const [count, setCount] = useState(0); const increment = () => { setCount(count + 1); }; const decrement = () => { setCount(count - 1); }; const reset = () => { setCount(0); }; return ( <div> <h1>Count: {count}</h1> <button onClick={decrement}>-</button> <button onClick={increment}>+</button> <button onClick={reset}>Reset</button> </div> ); } export default Counter;
State with Different Types
import { useState } from 'react'; function StateExamples() { // String state const [name, setName] = useState(''); // Boolean state const [isLoggedIn, setIsLoggedIn] = useState(false); // Array state const [items, setItems] = useState(['Apple', 'Banana']); // Object state const [user, setUser] = useState({ name: 'Om', email: '[email protected]' }); // Update array - add item const addItem = () => { setItems([...items, 'Orange']); }; // Update array - remove item const removeItem = (index) => { setItems(items.filter((_, i) => i !== index)); }; // Update object - partial update const updateEmail = () => { setUser({ ...user, email: '[email protected]' }); }; return ( <div> <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Enter name" /> <p>Hello, {name || 'Guest'}!</p> <button onClick={() => setIsLoggedIn(!isLoggedIn)}> {isLoggedIn ? 'Logout' : 'Login'} </button> <ul> {items.map((item, i) => <li key={i}>{item}</li>)} </ul> <button onClick={addItem}>Add Item</button> </div> ); }
Never Mutate State Directly!
Always create a new copy of arrays/objects when updating. Use spread operator: [...array] or {...object}
React Hooks
Hooks are functions that let you use state and other React features in function components.
| Hook | Purpose | Example |
|---|---|---|
useState |
Manage component state | const [count, setCount] = useState(0) |
useEffect |
Side effects (API calls, subscriptions) | useEffect(() => { ... }, [deps]) |
useContext |
Access context values | const theme = useContext(ThemeContext) |
useRef |
Reference DOM elements or persist values | const inputRef = useRef(null) |
useMemo |
Memoize expensive calculations | const value = useMemo(() => compute(), [deps]) |
useCallback |
Memoize functions | const fn = useCallback(() => {}, [deps]) |
useRef Hook
import { useRef } from 'react'; function FocusInput() { const inputRef = useRef(null); const focusInput = () => { inputRef.current.focus(); }; return ( <div> <input ref={inputRef} type="text" /> <button onClick={focusInput}>Focus Input</button> </div> ); }
Rules of Hooks
1. Only call hooks at the top level (not inside loops, conditions, or nested functions)
2. Only call hooks from React function components or custom hooks
Event Handling
React events are named using camelCase and you pass a function as the event handler.
import { useState } from 'react'; function EventExamples() { const [message, setMessage] = useState(''); // Click event const handleClick = () => { alert('Button clicked!'); }; // Click with parameter const handleGreet = (name) => { alert(`Hello, ${name}!`); }; // Event object const handleInputChange = (event) => { setMessage(event.target.value); }; // Prevent default behavior const handleSubmit = (event) => { event.preventDefault(); console.log('Form submitted!'); }; return ( <div> {/* Basic click */} <button onClick={handleClick}>Click Me</button> {/* Click with parameter - use arrow function */} <button onClick={() => handleGreet('Om')}>Greet</button> {/* Input change */} <input type="text" value={message} onChange={handleInputChange} onFocus={() => console.log('Focused!')} onBlur={() => console.log('Blurred!')} /> {/* Form submit */} <form onSubmit={handleSubmit}> <button type="submit">Submit</button> </form> {/* Mouse events */} <div onMouseEnter={() => console.log('Mouse entered')} onMouseLeave={() => console.log('Mouse left')} > Hover over me </div> </div> ); }
Conditional Rendering
import { useState } from 'react'; function ConditionalRendering() { const [isLoggedIn, setIsLoggedIn] = useState(false); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); return ( <div> {/* Method 1: If-Else with ternary */} {isLoggedIn ? ( <h1>Welcome back!</h1> ) : ( <h1>Please log in</h1> )} {/* Method 2: Logical AND (&&) - render if true */} {isLoggedIn && <button>Logout</button>} {/* Method 3: Logical OR (||) - render if false/empty */} {items.length || <p>No items found</p>} {/* Method 4: Nullish coalescing */} <p>{name ?? 'Anonymous'}</p> {/* Method 5: Multiple conditions */} {loading ? ( <p>Loading...</p> ) : items.length > 0 ? ( <ul> {items.map(item => <li key={item.id}>{item.name}</li>)} </ul> ) : ( <p>No items available</p> )} <button onClick={() => setIsLoggedIn(!isLoggedIn)}> Toggle Login </button> </div> ); }
Lists & Keys
import { useState } from 'react'; function TodoList() { const [todos, setTodos] = useState([ { id: 1, text: 'Learn React', completed: false }, { id: 2, text: 'Build Projects', completed: false }, { id: 3, text: 'Get a Job', completed: false }, ]); const [newTodo, setNewTodo] = useState(''); // Add new todo const addTodo = () => { if (newTodo.trim()) { setTodos([ ...todos, { id: Date.now(), text: newTodo, completed: false } ]); setNewTodo(''); } }; // Toggle complete const toggleTodo = (id) => { setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo )); }; // Delete todo const deleteTodo = (id) => { setTodos(todos.filter(todo => todo.id !== id)); }; return ( <div> <h1>Todo List</h1> <div> <input value={newTodo} onChange={(e) => setNewTodo(e.target.value)} placeholder="Add new todo" /> <button onClick={addTodo}>Add</button> </div> <ul> {todos.map(todo => ( {/* Always use unique key! */} <li key={todo.id}> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }} onClick={() => toggleTodo(todo.id)} > {todo.text} </span> <button onClick={() => deleteTodo(todo.id)}>Delete</button> </li> ))} </ul> <p>Total: {todos.length} | Completed: {todos.filter(t => t.completed).length}</p> </div> ); }
Keys are Important!
Always use unique, stable keys when rendering lists. Don't use array index if the list can change. Use id from your data.
Forms & Input
import { useState } from 'react'; function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', message: '', subscribe: false, country: 'nepal' }); const [errors, setErrors] = useState({}); // Handle all input changes const handleChange = (e) => { const { name, value, type, checked } = e.target; setFormData({ ...formData, [name]: type === 'checkbox' ? checked : value }); }; // Validate form const validate = () => { const newErrors = {}; if (!formData.name) newErrors.name = 'Name is required'; if (!formData.email.includes('@')) newErrors.email = 'Invalid email'; if (formData.message.length < 10) newErrors.message = 'Message too short'; setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // Handle submit const handleSubmit = (e) => { e.preventDefault(); if (validate()) { console.log('Form submitted:', formData); // Reset form setFormData({ name: '', email: '', message: '', subscribe: false, country: 'nepal' }); } }; return ( <form onSubmit={handleSubmit}> <div> <label>Name:</label> <input type="text" name="name" value={formData.name} onChange={handleChange} /> {errors.name && <span className="error">{errors.name}</span>} </div> <div> <label>Email:</label> <input type="email" name="email" value={formData.email} onChange={handleChange} /> {errors.email && <span className="error">{errors.email}</span>} </div> <div> <label>Country:</label> <select name="country" value={formData.country} onChange={handleChange}> <option value="nepal">Nepal</option> <option value="india">India</option> <option value="usa">USA</option> </select> </div> <div> <label>Message:</label> <textarea name="message" value={formData.message} onChange={handleChange} /> </div> <div> <label> <input type="checkbox" name="subscribe" checked={formData.subscribe} onChange={handleChange} /> Subscribe to newsletter </label> </div> <button type="submit">Submit</button> </form> ); }
useEffect & Side Effects
useEffect lets you perform side effects in function components - data fetching, subscriptions, manually changing the DOM, etc.
import { useState, useEffect } from 'react'; function UserList() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Fetch data on component mount useEffect(() => { const fetchUsers = async () => { try { const response = await fetch('https://jsonplaceholder.typicode.com/users'); if (!response.ok) throw new Error('Failed to fetch'); const data = await response.json(); setUsers(data); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchUsers(); }, []); // Empty array = run once on mount if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error}</p>; return ( <ul> {users.map(user => ( <li key={user.id}>{user.name} - {user.email}</li> ))} </ul> ); }
// Pattern 1: Run on every render (no dependency array) useEffect(() => { console.log('Runs on every render'); }); // Pattern 2: Run once on mount (empty array) useEffect(() => { console.log('Runs only once on mount'); }, []); // Pattern 3: Run when dependency changes useEffect(() => { console.log(`Count changed to: ${count}`); }, [count]); // Pattern 4: Cleanup function (return) useEffect(() => { const timer = setInterval(() => { console.log('Tick'); }, 1000); // Cleanup when component unmounts or before re-run return () => { clearInterval(timer); console.log('Cleaned up!'); }; }, []);
Context API
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
import { createContext, useState, useContext } from 'react'; // 1. Create Context const ThemeContext = createContext(); // 2. Create Provider Component export function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(theme === 'light' ? 'dark' : 'light'); }; return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); } // 3. Custom Hook for easy access export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within ThemeProvider'); } return context; }
// App.jsx - Wrap with Provider import { ThemeProvider } from './context/ThemeContext'; function App() { return ( <ThemeProvider> <Navbar /> <MainContent /> </ThemeProvider> ); } // Navbar.jsx - Use context import { useTheme } from './context/ThemeContext'; function Navbar() { const { theme, toggleTheme } = useTheme(); return ( <nav className={`navbar ${theme}`}> <h1>My App</h1> <button onClick={toggleTheme}> Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode </button> </nav> ); }
React Router
import { BrowserRouter, Routes, Route, Link, useParams, useNavigate } from 'react-router-dom'; // Pages function Home() { return <h1>Home Page</h1>; } function About() { return <h1>About Page</h1>; } function UserProfile() { const { userId } = useParams(); const navigate = useNavigate(); return ( <div> <h1>User Profile: {userId}</h1> <button onClick={() => navigate('/')}>Go Home</button> <button onClick={() => navigate(-1)}>Go Back</button> </div> ); } function NotFound() { return <h1>404 - Page Not Found</h1>; } // Navigation function Navbar() { return ( <nav> <Link to="/">Home</Link> <Link to="/about">About</Link> <Link to="/user/123">Profile</Link> </nav> ); } // App with Router function App() { return ( <BrowserRouter> <Navbar /> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/user/:userId" element={<UserProfile />} /> <Route path="*" element={<NotFound />} /> </Routes> </BrowserRouter> ); } export default App;
Advanced Patterns
Custom Hooks
import { useState, useEffect } from 'react'; // Custom hook for data fetching function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const response = await fetch(url); if (!response.ok) throw new Error('Failed to fetch'); const json = await response.json(); setData(json); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchData(); }, [url]); return { data, loading, error }; } export default useFetch; // Usage function UserList() { const { data: users, loading, error } = useFetch('https://api.example.com/users'); if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error}</p>; return <ul>{users?.map(u => <li key={u.id}>{u.name}</li>)}</ul>; }
useLocalStorage Hook
import { useState } from 'react'; function useLocalStorage(key, initialValue) { const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { return initialValue; } }); const setValue = (value) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error(error); } }; return [storedValue, setValue]; } export default useLocalStorage; // Usage function Settings() { const [theme, setTheme] = useLocalStorage('theme', 'light'); const [user, setUser] = useLocalStorage('user', { name: '' }); return ( <div> <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> Current: {theme} </button> </div> ); }
Performance Optimization
import { useState, useMemo, useCallback, memo } from 'react'; // useMemo - Memoize expensive calculations function ExpensiveList({ items, filter }) { const filteredItems = useMemo(() => { console.log('Filtering items...'); return items.filter(item => item.includes(filter)); }, [items, filter]); // Only recalculate when items or filter changes return ( <ul> {filteredItems.map((item, i) => <li key={i}>{item}</li>)} </ul> ); } // useCallback - Memoize functions function Parent() { const [count, setCount] = useState(0); // Without useCallback, this creates new function on every render const handleClick = useCallback(() => { console.log('Button clicked!'); }, []); // Empty deps = function never changes return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <MemoizedChild onClick={handleClick} /> </div> ); } // React.memo - Prevent unnecessary re-renders const MemoizedChild = memo(function Child({ onClick }) { console.log('Child rendered'); return <button onClick={onClick}>Click me</button>; }); // Lazy Loading Components import { lazy, Suspense } from 'react'; const HeavyComponent = lazy(() => import('./HeavyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <HeavyComponent /> </Suspense> ); }
Error Boundaries
import { Component } from 'react'; // Error Boundaries must be class components 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 caught:', error, errorInfo); // Log to error reporting service } render() { if (this.state.hasError) { return ( <div className="error-fallback"> <h2>Something went wrong!</h2> <button onClick={() => this.setState({ hasError: false })}> Try Again </button> </div> ); } return this.props.children; } } export default ErrorBoundary; // Usage in App.jsx function App() { return ( <ErrorBoundary> <MyComponent /> </ErrorBoundary> ); }
Recommended Project Structure
You're Ready!
You now have all the knowledge to build modern React applications. Practice by building projects!
Congratulations!
You've mastered React.js from zero to hero! You're now ready to build amazing user interfaces. Keep practicing and building projects!