React JS Tutorial
Build modern user interfaces with the most popular JavaScript library
1 What is React?
React is a JavaScript library for building user interfaces, created by Facebook. It uses a component-based architecture and a virtual DOM for efficient updates.
🔑 Why React?
Component-Based: Build encapsulated components that manage their own state
Declarative: Describe what UI should look like, React handles updates
Virtual DOM: Efficient updates by comparing virtual and real DOM
Large Ecosystem: Rich tooling, libraries, and community support
React vs Vanilla JavaScript
// Vanilla JavaScript - Manual DOM manipulation const button = document.getElementById('counter-btn'); const display = document.getElementById('count'); let count = 0; button.addEventListener('click', () => { count++; display.textContent = count; // Manual update });
// React - Declarative approach function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); }
2 Environment Setup
The easiest way to start a React project is using Create React App or the newer Vite.
# Option 1: Create React App (traditional) npx create-react-app my-app cd my-app npm start # Option 2: Vite (faster, recommended) npm create vite@latest my-app -- --template react cd my-app npm install npm run dev # Project structure created: my-app/ ├── node_modules/ ├── public/ ├── src/ │ ├── App.jsx # Main component │ ├── main.jsx # Entry point │ └── index.css ├── package.json └── vite.config.js
💡 Vite vs Create React App
Vite - Faster dev server, instant HMR, smaller builds (recommended for new projects)
CRA - More mature, wider compatibility, extensive documentation
3 JSX Syntax
JSX is a syntax extension that lets you write HTML-like code in JavaScript. It gets compiled to regular JavaScript function calls.
// JSX looks like HTML but it's JavaScript! const element = <h1>Hello, World!</h1>; // Embedding expressions with {} const name = "Alice"; const greeting = <h1>Hello, {name}!</h1>; // Any JavaScript expression works const math = <p>2 + 2 = {2 + 2}</p>; // Calling functions const upper = <p>{name.toUpperCase()}</p>; // JSX attributes use camelCase const link = <a href="https://react.dev" className="link">React Docs</a>; // Note: className instead of class! // Inline styles use object syntax const styled = <div style={{ color: 'blue', fontSize: '20px' }}> Styled text </div>; // Self-closing tags must have / const image = <img src="photo.jpg" alt="Photo" />; const input = <input type="text" />; // Multiple elements need a wrapper (or Fragment) const multiple = ( <> <h1>Title</h1> <p>Paragraph</p> </> );
⚠️ JSX Rules
• Use className not class
• Use htmlFor not for
• All tags must be closed: <br />, <img />
• Return one parent element (use <></> Fragment if needed)
4 Functional Components
Components are the building blocks of React apps. They're JavaScript functions that return JSX.
// Simple component (function that returns JSX) function Welcome() { return <h1>Welcome to React!</h1>; } // Arrow function syntax const Greeting = () => { return <p>Hello there!</p>; }; // Component with multiple elements function UserCard() { return ( <div className="card"> <img src="avatar.jpg" alt="Avatar" /> <h2>John Doe</h2> <p>Frontend Developer</p> </div> ); } // Using components (like HTML tags) function App() { return ( <div> <Welcome /> <Greeting /> <UserCard /> <UserCard /> {/* Reusable! */} </div> ); }
Welcome to React!
Hello there!
John Doe
Frontend Developer
John Doe
Frontend Developer
5 Props (Properties)
Props are how you pass data from parent to child components. They're read-only!
// Component receiving props function Greeting(props) { return <h1>Hello, {props.name}!</h1>; } // Destructuring props (cleaner) function UserCard({ name, role, avatar }) { return ( <div className="card"> <img src={avatar} alt={name} /> <h2>{name}</h2> <p>{role}</p> </div> ); } // Default props function Button({ text = "Click Me", color = "blue" }) { return ( <button style={{ backgroundColor: color }}> {text} </button> ); } // Using components with props function App() { return ( <div> <Greeting name="Alice" /> <Greeting name="Bob" /> <UserCard name="Alice Johnson" role="Developer" avatar="alice.jpg" /> <Button /> {/* Uses defaults */} <Button text="Submit" color="green" /> </div> ); }
Hello, Alice!
Hello, Bob!
6 Children Props
The special children prop contains whatever you put between opening and closing tags of a component.
// Wrapper component using children function Card({ children, title }) { return ( <div className="card"> <h3>{title}</h3> <div className="card-body"> {children} {/* Content goes here */} </div> </div> ); } // Layout component function Layout({ children }) { return ( <div className="layout"> <header>My App</header> <main>{children}</main> <footer>© 2024</footer> </div> ); } // Usage function App() { return ( <Layout> <Card title="Welcome"> <p>This is the card content.</p> <button>Click Me</button> </Card> <Card title="Another Card"> <ul> <li>Item 1</li> <li>Item 2</li> </ul> </Card> </Layout> ); }
7 useState Hook
useState lets components "remember" information. When state changes, React re-renders the component.
import { useState } from 'react'; // Counter example function Counter() { // [currentValue, setterFunction] = useState(initialValue) const [count, setCount] = useState(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> ); } // Multiple state variables function Form() { const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [agreed, setAgreed] = useState(false); return ( <form> <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" /> <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" /> <label> <input type="checkbox" checked={agreed} onChange={(e) => setAgreed(e.target.checked)} /> I agree to terms </label> </form> ); } // State with objects function UserProfile() { const [user, setUser] = useState({ name: "Alice", age: 25, city: "NYC" }); // Updating one property (spread operator!) const updateAge = () => { setUser({ ...user, age: user.age + 1 }); }; return ( <div> <p>{user.name}, {user.age}</p> <button onClick={updateAge}>Birthday!</button> </div> ); }
⚠️ State Rules
• Don't modify state directly: count++ ❌ → setCount(count + 1) ✅
• State updates are async (batched for performance)
• For objects/arrays, always create new copies with spread
8 useEffect Hook
useEffect handles side effects - operations like fetching data, subscriptions, or DOM manipulation.
import { useState, useEffect } from 'react'; function DataFetcher() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); // Runs after every render useEffect(() => { console.log("Component rendered!"); }); // Runs ONCE on mount (empty dependency array) useEffect(() => { fetch("https://api.example.com/data") .then(res => res.json()) .then(data => { setData(data); setLoading(false); }); }, []); // Empty array = run once if (loading) return <p>Loading...</p>; return <div>{JSON.stringify(data)}</div>; } // Runs when specific values change function SearchResults({ query }) { const [results, setResults] = useState([]); useEffect(() => { console.log(`Searching for: ${query}`); // Fetch new results when query changes searchAPI(query).then(setResults); }, [query]); // Re-run when query changes return ( <ul> {results.map(r => <li key={r.id}>{r.name}</li>)} </ul> ); } // Cleanup function (for subscriptions, timers) function Timer() { const [seconds, setSeconds] = useState(0); useEffect(() => { const interval = setInterval(() => { setSeconds(s => s + 1); }, 1000); // Cleanup: runs when component unmounts return () => clearInterval(interval); }, []); return <p>Timer: {seconds}s</p>; }
💡 useEffect Dependency Array
useEffect(() => {}, []) - Run once on mount
useEffect(() => {}) - Run after every render
useEffect(() => {}, [x, y]) - Run when x or y changes
9 useRef Hook
useRef creates a mutable reference that persists across renders without causing re-renders.
import { useRef } from 'react'; // Access DOM elements function FocusInput() { const inputRef = useRef(null); const handleClick = () => { inputRef.current.focus(); // Focus the input }; return ( <div> <input ref={inputRef} placeholder="Click button to focus" /> <button onClick={handleClick}>Focus Input</button> </div> ); } // Store mutable values 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: {time}s</p> <button onClick={start}>Start</button> <button onClick={stop}>Stop</button> </div> ); }
10 useContext Hook
Context provides a way to pass data through the component tree without prop drilling.
import { createContext, useContext, useState } from 'react'; // 1. Create context const ThemeContext = createContext("light"); // 2. Provider component function App() { const [theme, setTheme] = useState("light"); return ( <ThemeContext.Provider value={{ theme, setTheme }}> <Header /> <Main /> </ThemeContext.Provider> ); } // 3. Consume context with useContext function Header() { const { theme, setTheme } = useContext(ThemeContext); return ( <header className={theme}> <h1>My App ({theme} mode)</h1> <button onClick={() => setTheme(theme === "light" ? "dark" : "light") }> Toggle Theme </button> </header> ); } // Deep nested component can still access context! function DeepChild() { const { theme } = useContext(ThemeContext); return <div className={`card ${theme}`}>Styled by context</div>; }
11 Lists & Keys
When rendering lists, each item needs a unique key prop for React to track changes efficiently.
function TodoList() { const [todos, setTodos] = useState([ { id: 1, text: "Learn React", done: false }, { id: 2, text: "Build project", done: false }, { id: 3, text: "Deploy app", done: true } ]); return ( <ul> {todos.map(todo => ( {/* key must be unique and stable */} <li key={todo.id}> <input type="checkbox" checked={todo.done} onChange={() => toggleTodo(todo.id)} /> {todo.text} </li> ))} </ul> ); } // Filtering lists function FilteredList({ items, filter }) { const filtered = items .filter(item => item.category === filter) .map(item => ( <div key={item.id}>{item.name}</div> )); return <div>{filtered}</div>; }
⚠️ Key Rules
• Keys must be unique among siblings
• Don't use array index as key (causes bugs on reorder)
• Use stable IDs from your data
12 Form Handling
React forms use "controlled components" where form data is handled by React state.
function SignupForm() { const [formData, setFormData] = useState({ username: "", email: "", password: "" }); const [errors, setErrors] = useState({}); // Generic handler for all inputs const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = (e) => { e.preventDefault(); // Validation const newErrors = {}; if (!formData.username) newErrors.username = "Required"; if (!formData.email) newErrors.email = "Required"; if (formData.password.length < 6) { newErrors.password = "Min 6 characters"; } if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } console.log("Submitting:", formData); }; return ( <form onSubmit={handleSubmit}> <div> <input name="username" value={formData.username} onChange={handleChange} placeholder="Username" /> {errors.username && <span className="error">{errors.username}</span>} </div> <div> <input name="email" type="email" value={formData.email} onChange={handleChange} placeholder="Email" /> {errors.email && <span className="error">{errors.email}</span>} </div> <button type="submit">Sign Up</button> </form> ); }
13 Conditional Rendering
Show different content based on conditions using JavaScript operators in JSX.
function Dashboard({ user, loading, error }) { // Early return pattern if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error}</p>; return ( <div> {/* Ternary operator */} {user ? ( <h1>Welcome, {user.name}!</h1> ) : ( <h1>Please log in</h1> )} {/* && operator (show if truthy) */} {user && user.isAdmin && ( <button>Admin Panel</button> )} {/* Multiple conditions */} {user?.notifications?.length > 0 && ( <span className="badge"> {user.notifications.length} </span> )} </div> ); } // Conditional CSS classes function Button({ disabled, primary }) { return ( <button className={`btn ${primary ? 'btn-primary' : ''} ${disabled ? 'disabled' : ''}`} disabled={disabled} > Click Me </button> ); }
14 Data Fetching
Fetch data from APIs using useEffect and useState.
function UserList() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); 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(); }, []); if (loading) return <div className="spinner">Loading...</div>; if (error) return <div className="error">Error: {error}</div>; return ( <ul> {users.map(user => ( <li key={user.id}> <strong>{user.name}</strong> <p>{user.email}</p> </li> ))} </ul> ); }
15 Custom Hooks
Extract reusable logic into custom hooks. They always start with "use".
// Custom hook for data fetching function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetch(url) .then(res => res.json()) .then(setData) .catch(setError) .finally(() => setLoading(false)); }, [url]); return { data, loading, error }; } // Usage function Posts() { const { data, loading, error } = useFetch("/api/posts"); if (loading) return <p>Loading...</p>; return <div>{data.map(p => <Post key={p.id} {...p} />)}</div>; } // Custom hook for localStorage function useLocalStorage(key, initialValue) { const [value, setValue] = useState(() => { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : initialValue; }); useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue]; } // Usage function Settings() { const [theme, setTheme] = useLocalStorage("theme", "light"); // theme persists across page refreshes! }
16 React Router
React Router enables navigation between pages in single-page applications.
// npm install react-router-dom import { BrowserRouter, Routes, Route, Link, useParams, useNavigate } from 'react-router-dom'; // App with routing function App() { return ( <BrowserRouter> {/* Navigation */} <nav> <Link to="/">Home</Link> <Link to="/about">About</Link> <Link to="/users">Users</Link> </nav> {/* Route definitions */} <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/users" element={<Users />} /> <Route path="/users/:id" element={<UserDetail />} /> <Route path="*" element={<NotFound />} /> </Routes> </BrowserRouter> ); } // Dynamic route with useParams function UserDetail() { const { id } = useParams(); return <h1>User ID: {id}</h1>; } // Programmatic navigation function LoginForm() { const navigate = useNavigate(); const handleSubmit = () => { // After login success navigate("/dashboard"); }; return <button onClick={handleSubmit}>Login</button>; }
17 Performance Optimization
React provides tools to optimize rendering and prevent unnecessary re-renders.
import { useState, useMemo, useCallback, memo } from 'react'; // useMemo - memoize expensive calculations function ExpensiveList({ items, filter }) { const filteredItems = useMemo(() => { console.log("Filtering..."); return items.filter(item => item.name.includes(filter) ); }, [items, filter]); // Only recalculate when these change return ( <ul> {filteredItems.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> ); } // useCallback - memoize functions function Parent() { const [count, setCount] = useState(0); // Without useCallback, new function created every render const handleClick = useCallback(() => { console.log("Clicked!"); }, []); // Empty deps = same function reference return <Child onClick={handleClick} />; } // memo - prevent re-render if props unchanged const Child = memo(function Child({ onClick }) { console.log("Child rendered"); return <button onClick={onClick}>Click</button>; }); // Lazy loading components import { lazy, Suspense } from 'react'; const HeavyComponent = lazy(() => import('./HeavyComponent')); function App() { return ( <Suspense fallback={<p>Loading...</p>}> <HeavyComponent /> </Suspense> ); }
💡 When to Optimize
• useMemo - Expensive calculations (sorting, filtering large arrays)
• useCallback - Functions passed to memoized children
• memo - Components that render often with same props
• Don't optimize prematurely! Measure first.