Core React Concepts
Master fundamental React concepts using functional components and Hooks
Core React Concepts
Master React fundamentals with free flashcards and spaced repetition practice. This lesson covers components, JSX, props, state, and the virtual DOMβessential concepts for building modern web applications with React.
Welcome to React π»
React has revolutionized how we build user interfaces. Created by Facebook in 2013, React introduced a component-based architecture that makes building complex UIs manageable and maintainable. Whether you're building a small personal project or a large-scale application, understanding React's core concepts is your foundation for success.
π€ Did you know? React was first deployed on Facebook's newsfeed in 2011 and Instagram.com in 2012 before being open-sourced. Today, it powers thousands of production applications including Netflix, Airbnb, and Discord.
Core Concepts Explained
1. Components π§©
Components are the building blocks of React applications. Think of them as custom, reusable HTML elements that encapsulate their own structure, styling, and behavior.
π Real-world analogy: Components are like LEGO blocks. Each block (component) has a specific shape and purpose, but you can combine them in countless ways to build complex structures (applications).
There are two types of components:
Functional Components (modern approach):
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
Class Components (legacy approach):
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
π‘ Tip: Since React 16.8 (2019), functional components with Hooks are the preferred approach. They're simpler, more concise, and easier to test.
2. JSX (JavaScript XML) π
JSX is a syntax extension that lets you write HTML-like code directly in JavaScript. It looks like HTML but gets transformed into JavaScript function calls.
// JSX syntax
const element = <h1 className="greeting">Hello, world!</h1>;
// What it compiles to
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
Key JSX Rules:
| Rule | Example | Why? |
|---|---|---|
Use className not class | <div className="box"> | class is a JavaScript keyword |
| Close all tags | <img />, <br /> | JSX requires valid XML syntax |
| Use camelCase for attributes | onClick, onChange | JavaScript naming convention |
| One parent element | <>...</> or <div>...</div> | JSX expressions must have one root |
π§ Memory device: "CamelCase for Clicks" - remember to use onClick, not onclick.
3. Props (Properties) π¦
Props are how components talk to each other. They're read-only data passed from parent to child components, like function parameters.
function UserCard(props) {
return (
<div className="card">
<h2>{props.name}</h2>
<p>Age: {props.age}</p>
<p>Role: {props.role}</p>
</div>
);
}
// Usage
<UserCard name="Alice" age={28} role="Developer" />
Props Flow Visualization:
βββββββββββββββββββββββββββββββββββββββ
β App Component (Parent) β
β β
β const user = { β
β name: "Alice", β
β age: 28 β
β } β
β β
β Passes data β β
ββββββββββββββββ¬βββββββββββββββββββββββ
β props={user}
β
βββββββββββββββββββββββββββββββββββββββ
β UserCard Component (Child) β
β β
β Receives: props.name, props.age β
β β οΈ Cannot modify props! β
βββββββββββββββββββββββββββββββββββββββ
π‘ Tip: Use destructuring for cleaner code:
function UserCard({ name, age, role }) {
return <div>{name} is {age}</div>;
}
4. State π
State is private data that belongs to a component and can change over time. When state changes, React automatically re-renders the component.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
State vs Props Comparison:
| Aspect | Props | State |
|---|---|---|
| Controlled by | Parent component | Component itself |
| Can change? | β No (read-only) | β Yes (via setState) |
| Triggers re-render? | β When parent changes | β When state updates |
| Use case | Configuration, data flow | Interactive data, UI changes |
π§ Memory device: "Props Pass down, State Stays local"
5. Virtual DOM π
The Virtual DOM is React's secret weapon for performance. It's a lightweight JavaScript representation of the actual DOM.
How it works:
ββββββββββββββββββββββββββββββββββββββββββββββββ
β REACT UPDATE PROCESS β
ββββββββββββββββββββββββββββββββββββββββββββββββ
Step 1: State/Props Change
β
β
βββββββββββββββββββββββ
β Create new β
β Virtual DOM β
ββββββββββββ¬βββββββββββ
β
β
βββββββββββββββββββββββ
β Diff Algorithm β
β (Reconciliation) β
β Compare old vs β
β new Virtual DOM β
ββββββββββββ¬βββββββββββ
β
β
βββββββββββββββββββββββ
β Calculate β
β Minimum Changes β
ββββββββββββ¬βββββββββββ
β
β
βββββββββββββββββββββββ
β Update Real DOM β
β (Batch Updates) β
βββββββββββββββββββββββ
π Real-world analogy: Imagine you're editing a document. Instead of reprinting the entire document after each change, you mark only the changes and update just those sections. That's what the Virtual DOM does for web pages.
6. Component Lifecycle & Effects β°
Components go through different phases: mounting (creation), updating (changes), and unmounting (removal).
With Hooks, we use useEffect to handle side effects:
import { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
// Runs after component mounts
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => setData(data));
// Cleanup function (runs before unmount)
return () => {
console.log('Component unmounting');
};
}, []); // Empty array = run once on mount
return <div>{data ? data.title : 'Loading...'}</div>;
}
useEffect Dependency Array:
| Syntax | When Effect Runs | Use Case |
|---|---|---|
useEffect(() => {...}) | After every render | β οΈ Rarely needed, can cause issues |
useEffect(() => {...}, []) | Once on mount | API calls, subscriptions |
useEffect(() => {...}, [dep]) | When dep changes | React to specific changes |
Detailed Examples with Explanations
Example 1: Building a Todo List π
Let's build a complete todo application demonstrating multiple concepts:
import { useState } from 'react';
function TodoApp() {
// State management
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
// Event handler
const handleAddTodo = () => {
if (inputValue.trim()) {
setTodos([...todos, {
id: Date.now(),
text: inputValue,
completed: false
}]);
setInputValue(''); // Clear input
}
};
// Toggle completion
const handleToggle = (id) => {
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
));
};
// Delete todo
const handleDelete = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div className="todo-app">
<h1>My Todo List</h1>
{/* Input section */}
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAddTodo()}
placeholder="Add a new task..."
/>
<button onClick={handleAddTodo}>Add</button>
</div>
{/* Todo list */}
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</ul>
{/* Stats */}
<p>Total: {todos.length} |
Completed: {todos.filter(t => t.completed).length}
</p>
</div>
);
}
// Separate component for each todo item
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
}
Key concepts demonstrated:
- β
Multiple state variables (
todos,inputValue) - β
Event handlers (
onChange,onClick,onKeyPress) - β Conditional rendering (line-through style)
- β
List rendering with
map() - β Props passing to child component
- β
Immutable state updates (spread operator,
filter,map)
Example 2: Fetching and Displaying Data π
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Reset states when userId changes
setLoading(true);
setError(null);
// Fetch user data
fetch(`https://api.example.com/users/${userId}`)
.then(response => {
if (!response.ok) throw new Error('User not found');
return response.json();
})
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [userId]); // Re-run when userId changes
// Loading state
if (loading) return <div>Loading user profile...</div>;
// Error state
if (error) return <div>Error: {error}</div>;
// Success state
return (
<div className="user-profile">
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
<p>Joined: {new Date(user.joinDate).toLocaleDateString()}</p>
</div>
);
}
Key concepts demonstrated:
- β
useEffectwith dependency array - β Handling loading, error, and success states
- β Conditional rendering (multiple return statements)
- β
Props (
userId) triggering effects - β Async operations in React
Example 3: Form Handling with Controlled Components π
import { useState } from 'react';
function RegistrationForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
agreeToTerms: false
});
const [errors, setErrors] = useState({});
// Generic handler for all inputs
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
// Validation
const validate = () => {
const newErrors = {};
if (formData.username.length < 3) {
newErrors.username = 'Username must be at least 3 characters';
}
if (!formData.email.includes('@')) {
newErrors.email = 'Invalid email address';
}
if (formData.password.length < 8) {
newErrors.password = 'Password must be at least 8 characters';
}
if (!formData.agreeToTerms) {
newErrors.agreeToTerms = 'You must agree to terms';
}
return newErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
const validationErrors = validate();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
// Submit form
console.log('Form submitted:', formData);
// API call would go here
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
{errors.username && <span className="error">{errors.username}</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>Password:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <span className="error">{errors.password}</span>}
</div>
<div>
<label>
<input
type="checkbox"
name="agreeToTerms"
checked={formData.agreeToTerms}
onChange={handleChange}
/>
I agree to the terms and conditions
</label>
{errors.agreeToTerms && <span className="error">{errors.agreeToTerms}</span>}
</div>
<button type="submit">Register</button>
</form>
);
}
Key concepts demonstrated:
- β Controlled components (React controls form state)
- β Single handler for multiple inputs
- β Form validation
- β Preventing default form submission
- β
Dynamic object property names
[name] - β Conditional rendering of errors
Example 4: Component Composition and Props.children π
// Reusable Card component
function Card({ title, children, footer }) {
return (
<div className="card">
{title && <div className="card-header">{title}</div>}
<div className="card-body">
{children}
</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
}
// Usage examples
function App() {
return (
<div>
{/* Simple card */}
<Card title="Welcome">
<p>This is card content</p>
</Card>
{/* Card with footer */}
<Card
title="User Stats"
footer={<button>View Details</button>}
>
<p>Posts: 42</p>
<p>Followers: 1,234</p>
</Card>
{/* Card with complex content */}
<Card title="Settings">
<form>
<input type="text" placeholder="Name" />
<input type="email" placeholder="Email" />
<button>Save</button>
</form>
</Card>
</div>
);
}
Key concepts demonstrated:
- β
props.childrenfor flexible composition - β Optional props with conditional rendering
- β Reusable wrapper components
- β Component composition over inheritance
β οΈ Common Mistakes
1. Mutating State Directly β
// β WRONG - Mutating state
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // This won't trigger re-render!
setItems(items); // Still wrong!
// β
CORRECT - Create new array
setItems([...items, 4]);
// or
setItems(items.concat(4));
2. Forgetting Keys in Lists β
// β WRONG - No key or using index as key
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}
// β
CORRECT - Use unique, stable identifier
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
π‘ Why? React uses keys to track which items changed. Using index can cause bugs when items are reordered or deleted.
3. Missing Dependencies in useEffect β
// β WRONG - Missing dependency
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count); // Uses 'count'
}, []); // But doesn't list it!
// β
CORRECT - Include all dependencies
useEffect(() => {
console.log(count);
}, [count]); // Now it re-runs when count changes
4. Using State Immediately After Setting It β
// β WRONG - State updates are asynchronous
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // Still shows old value!
};
// β
CORRECT - Use useEffect to react to changes
useEffect(() => {
console.log(count); // Shows updated value
}, [count]);
5. Not Using Functional Updates β
// β WRONG - Can cause bugs with multiple updates
const handleClick = () => {
setCount(count + 1);
setCount(count + 1); // Both use same 'count' value!
};
// β
CORRECT - Use functional form
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1); // Each gets latest value
};
π§ Try This: Mini-Exercise
Challenge: Modify the Counter example to include a "Reset" button and prevent the count from going below zero.
Starter code:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
{/* Add your code here */}
</div>
);
}
Solution:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(prev => Math.max(0, prev - 1))}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
Key Takeaways π―
β Components are reusable building blocks that return JSX
β JSX looks like HTML but is actually JavaScript with special rules
β Props flow down (parent to child) and are read-only
β State is local, mutable data that triggers re-renders when changed
β Virtual DOM makes React fast by minimizing actual DOM updates
β useEffect handles side effects like data fetching and subscriptions
β Never mutate state directly - always create new objects/arrays
β Keys in lists should be unique and stable identifiers
β State updates are asynchronous - don't rely on the value immediately
π Further Study
- Official React Documentation: https://react.dev/learn (comprehensive guide with interactive examples)
- React Hooks Deep Dive: https://react.dev/reference/react (complete API reference for all hooks)
- React Patterns: https://www.patterns.dev/posts/reactjs (advanced patterns and best practices)
π Quick Reference Card
| Concept | Key Points | Example |
|---|---|---|
| Component | Function returning JSX | function App() { return <div/>; } |
| Props | Read-only, passed from parent | <User name="Alice" /> |
| State | Mutable, triggers re-render | const [x, setX] = useState(0) |
| JSX | HTML-like syntax in JS | <div className="box">Hi</div> |
| useEffect | Side effects, runs after render | useEffect(() => {...}, [deps]) |
| Event Handler | Function responding to events | onClick={() => setX(x + 1)} |
| List Rendering | Map array to components | {items.map(i => <Li key={i.id}/>)} |
| Conditional Render | Show/hide based on condition | {isLogged && <Dashboard/>} |