Introduction
React is a powerful JavaScript library for building user interfaces. Created by Facebook, React has become the most popular choice for modern web development. It uses a component-based architecture and a virtual DOM for efficient rendering. In this lesson, you'll learn React fundamentals, hooks, state management, and how to build interactive applications.
Learning Objectives
By the end of this lesson, you will be able to:
- Understand React components and JSX syntax
- Manage component state with useState hook
- Handle side effects with useEffect hook
- Pass data between components using props
- Handle events in React applications
- Build a complete React application
Prerequisites
- Completion of Lessons 01-05
- Strong JavaScript fundamentals
- Understanding of ES6+ features
- Node.js and npm installed
Estimated Time
5 hours (including practice exercises)
Setting Up React
The easiest way to start a React project is using Create React App:
# Create new React app
npx create-react-app my-app
# Navigate to project
cd my-app
# Start development server
npm start
npm create vite@latest my-app -- --template react
JSX Basics
JSX is a syntax extension that looks like HTML but works in JavaScript:
// Basic JSX
const element = <h1>Hello, React!</h1>;
// JSX with expressions
const name = 'Alice';
const greeting = <h1>Hello, {name}!</h1>;
// JSX with attributes
const image = <img src="photo.jpg" alt="Profile" />;
// JSX with children
const card = (
<div className="card">
<h2>Title</h2>
<p>Description</p>
</div>
);
// JSX expressions
const isLoggedIn = true;
const message = (
<div>
{isLoggedIn ? <p>Welcome back!</p> : <p>Please log in</p>}
</div>
);
// JSX with arrays
const numbers = [1, 2, 3, 4, 5];
const listItems = (
<ul>
{numbers.map(num => <li key={num}>{num}</li>)}
</ul>
);
className instead of class, and htmlFor instead of for in JSX.
Function Components
Modern React uses function components with hooks:
// Simple component
function Welcome() {
return <h1>Hello, World!</h1>;
}
// Component with props
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// Destructured props
function UserCard({ name, email, avatar }) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);
}
// Using components
function App() {
return (
<div>
<Welcome />
<Greeting name="Alice" />
<UserCard
name="Bob"
email="[email protected]"
avatar="avatar.jpg"
/>
</div>
);
}
useState Hook
useState allows you to add state to function components:
import { useState } from 'react';
// Simple counter
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<button onClick={() => setCount(0)}>
Reset
</button>
</div>
);
}
// Multiple state variables
function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);
const handleSubmit = (e) => {
e.preventDefault();
console.log({ name, email, age });
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="number"
value={age}
onChange={(e) => setAge(Number(e.target.value))}
placeholder="Age"
/>
<button type="submit">Submit</button>
</form>
);
}
// Object state
function UserProfile() {
const [user, setUser] = useState({
name: 'Alice',
age: 25,
email: '[email protected]'
});
const updateName = (newName) => {
setUser(prevUser => ({
...prevUser,
name: newName
}));
};
return (
<div>
<h2>{user.name}</h2>
<p>Age: {user.age}</p>
<p>Email: {user.email}</p>
<button onClick={() => updateName('Bob')}>
Change Name
</button>
</div>
);
}
// Array state
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim()) {
setTodos([...todos, { id: Date.now(), text: input }]);
setInput('');
}
};
const removeTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo"
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>
Delete
</button>
</li>
))}
</ul>
</div>
);
}
useEffect Hook
useEffect handles side effects like data fetching, subscriptions, and DOM manipulation:
import { useState, useEffect } from 'react';
// Run once on mount
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
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>;
}
// Run when dependency changes
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`https://api.example.com/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]); // Re-run when userId changes
return user ? <div>{user.name}</div> : <p>Loading...</p>;
}
// Cleanup function
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// Cleanup
return () => clearInterval(interval);
}, []);
return <p>Seconds: {seconds}</p>;
}
// Multiple effects
function Dashboard() {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
// Fetch user
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(data => setUser(data));
}, []);
// Fetch posts when user loads
useEffect(() => {
if (user) {
fetch(`/api/posts?userId=${user.id}`)
.then(res => res.json())
.then(data => setPosts(data));
}
}, [user]);
return (
<div>
{user && <h2>{user.name}</h2>}
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
</div>
);
}
Event Handling
function EventExamples() {
const [text, setText] = useState('');
// Click event
const handleClick = () => {
alert('Button clicked!');
};
// Input change
const handleChange = (e) => {
setText(e.target.value);
};
// Form submit
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted:', text);
};
// Mouse events
const handleMouseEnter = () => {
console.log('Mouse entered');
};
// Keyboard events
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
console.log('Enter pressed');
}
};
return (
<div>
<button onClick={handleClick}>Click Me</button>
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={handleChange}
onKeyPress={handleKeyPress}
placeholder="Type something"
/>
<button type="submit">Submit</button>
</form>
<div
onMouseEnter={handleMouseEnter}
style={{ padding: '20px', background: '#f0f0f0' }}
>
Hover over me
</div>
</div>
);
}
Conditional Rendering
function ConditionalExamples() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// If-else with ternary
return (
<div>
{isLoggedIn ? (
<p>Welcome back!</p>
) : (
<p>Please log in</p>
)}
{/* Logical AND */}
{user && <p>Hello, {user.name}</p>}
{/* Multiple conditions */}
{loading ? (
<p>Loading...</p>
) : user ? (
<UserProfile user={user} />
) : (
<p>No user found</p>
)}
{/* Conditional classes */}
<button className={isLoggedIn ? 'active' : 'inactive'}>
Status
</button>
</div>
);
}
Lists and Keys
function ProductList() {
const products = [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Phone', price: 699 },
{ id: 3, name: 'Tablet', price: 499 }
];
return (
<div>
<h2>Products</h2>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
</div>
);
}
// Component for list items
function ProductItem({ product }) {
return (
<div className="product-card">
<h3>{product.name}</h3>
<p>${product.price}</p>
<button>Add to Cart</button>
</div>
);
}
function ProductGrid() {
const products = [...]; // product array
return (
<div className="grid">
{products.map(product => (
<ProductItem key={product.id} product={product} />
))}
</div>
);
}
key prop when rendering lists. Never use array index as key if items can be reordered.
Practical Example: Todo App
import { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const [filter, setFilter] = useState('all');
const addTodo = () => {
if (input.trim()) {
setTodos([
...todos,
{
id: Date.now(),
text: input,
completed: false
}
]);
setInput('');
}
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
const stats = {
total: todos.length,
active: todos.filter(t => !t.completed).length,
completed: todos.filter(t => t.completed).length
};
return (
<div className="todo-app">
<h1>Todo List</h1>
{/* Input */}
<div className="input-section">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="What needs to be done?"
/>
<button onClick={addTodo}>Add</button>
</div>
{/* Filters */}
<div className="filters">
<button
onClick={() => setFilter('all')}
className={filter === 'all' ? 'active' : ''}
>
All ({stats.total})
</button>
<button
onClick={() => setFilter('active')}
className={filter === 'active' ? 'active' : ''}
>
Active ({stats.active})
</button>
<button
onClick={() => setFilter('completed')}
className={filter === 'completed' ? 'active' : ''}
>
Completed ({stats.completed})
</button>
</div>
{/* Todo List */}
<ul className="todo-list">
{filteredTodos.map(todo => (
<li
key={todo.id}
className={todo.completed ? 'completed' : ''}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span onClick={() => toggleTodo(todo.id)}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>
Delete
</button>
</li>
))}
</ul>
{/* Empty state */}
{filteredTodos.length === 0 && (
<p className="empty-state">No todos to display</p>
)}
</div>
);
}
export default TodoApp;
Key Takeaways
- React uses a component-based architecture
- JSX combines HTML-like syntax with JavaScript
- useState manages component state
- useEffect handles side effects and lifecycle
- Props pass data from parent to child components
- Always provide unique keys when rendering lists
- Event handlers use camelCase (onClick, onChange)
- State updates are asynchronous
Practice Exercises
Exercise 1: Counter with History
Build a counter that tracks all previous values:
// Create a counter that:
// 1. Shows current count
// 2. Has increment/decrement buttons
// 3. Displays history of all values
// 4. Can undo to previous value
Exercise 2: User Search
Fetch and filter users from an API:
// Build a user search that:
// 1. Fetches users from JSONPlaceholder
// 2. Filters users as you type
// 3. Shows loading state
// 4. Handles errors gracefully
Exercise 3: Shopping Cart
Create a shopping cart with add/remove functionality:
// Build a cart that:
// 1. Displays products
// 2. Adds items to cart
// 3. Updates quantities
// 4. Calculates total price
// 5. Removes items
Additional Resources
What's Next?
In Lesson 07, you'll shift focus to backend development with Node.js and Express. You'll learn to create HTTP servers, implement routing, use middleware, and build RESTful API endpoints.