React Fundamentals: Complete Guide to Components, Props, and State
Master React basics with this comprehensive guide covering components, props, state management, and event handling. Perfect for beginners starting their React journey.
React Fundamentals: Complete Guide to Components, Props, and State
React is a powerful JavaScript library for building user interfaces, particularly single-page applications where you need a fast, interactive user experience. This comprehensive guide will take you through the fundamental concepts that form the foundation of React development.
What is React?
React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It was created by Facebook (now Meta) and has become one of the most popular frontend libraries in the web development ecosystem.
Key Benefits of React
- Component-Based Architecture: Build encapsulated components that manage their own state
- Virtual DOM: Efficient updates through virtual DOM diffing algorithm
- Declarative Programming: Describe what the UI should look like for any given state
- Large Ecosystem: Extensive community and third-party library support
- Developer Experience: Excellent debugging tools and development workflow
Setting Up Your First React Application
Before diving into React concepts, let's set up a new React application using Create React App:
# Create a new React app
npx create-react-app my-react-app
# Navigate to the project directory
cd my-react-app
# Start the development server
npm start
This will create a new React application with all the necessary dependencies and configuration.
Understanding JSX
JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like code in your JavaScript files. It's the primary way to describe the UI in React.
Basic JSX Syntax
// Simple JSX element
const element = <h1>Hello, React!</h1>;
// JSX with variables
const name = 'World';
const greeting = <h1>Hello, {name}!</h1>;
// JSX with expressions
const user = { firstName: 'John', lastName: 'Doe' };
const welcome = (
<h1>
Welcome, {user.firstName} {user.lastName}!
</h1>
);
JSX Rules and Best Practices
// 1. JSX must return a single parent element
const ValidComponent = () => {
return (
<div>
<h1>Title</h1>
<p>Content</p>
</div>
);
};
// 2. Use React.Fragment or shorthand <> for multiple elements
const FragmentExample = () => {
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
};
// 3. Use camelCase for HTML attributes
const AttributeExample = () => {
return (
<div className="container" onClick={handleClick}>
<label htmlFor="email">Email:</label>
<input type="email" id="email" />
</div>
);
};
// 4. Close all tags
const SelfClosingTags = () => {
return (
<div>
<img src="image.jpg" alt="Description" />
<input type="text" />
<br />
</div>
);
};
React Components
Components are the building blocks of React applications. They let you split the UI into independent, reusable pieces.
Function Components
Function components are the modern way to create React components:
// Simple function component
function Welcome() {
return <h1>Welcome to React!</h1>;
}
// Arrow function component
const Greeting = () => {
return <h1>Hello from Arrow Function!</h1>;
};
// Component with parameters
const PersonalGreeting = ({ name }) => {
return <h1>Hello, {name}!</h1>;
};
// Using the components
const App = () => {
return (
<div>
<Welcome />
<Greeting />
<PersonalGreeting name="Alice" />
</div>
);
};
Component Composition
// Header component
const Header = () => {
return (
<header className="app-header">
<h1>My React App</h1>
<nav>
<a href="#home">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
</nav>
</header>
);
};
// Main content component
const MainContent = () => {
return (
<main className="main-content">
<h2>Welcome to Our Website</h2>
<p>This is the main content area.</p>
</main>
);
};
// Footer component
const Footer = () => {
return (
<footer className="app-footer">
<p>© 2024 My React App. All rights reserved.</p>
</footer>
);
};
// App component using composition
const App = () => {
return (
<div className="app">
<Header />
<MainContent />
<Footer />
</div>
);
};
Understanding Props
Props (short for properties) are how components receive data from their parent components. They make components reusable and dynamic.
Basic Props Usage
// Component that receives props
const UserCard = (props) => {
return (
<div className="user-card">
<h3>{props.name}</h3>
<p>Age: {props.age}</p>
<p>Email: {props.email}</p>
</div>
);
};
// Using the component with props
const App = () => {
return (
<div>
<UserCard name="John Doe" age={30} email="john@example.com" />
<UserCard name="Jane Smith" age={25} email="jane@example.com" />
</div>
);
};
Props Destructuring
// Destructuring props in function parameters
const UserCard = ({ name, age, email, avatar }) => {
return (
<div className="user-card">
<img src={avatar} alt={`${name} avatar`} />
<h3>{name}</h3>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
};
// Default props
const Button = ({ text = 'Click me', type = 'button', onClick }) => {
return (
<button type={type} onClick={onClick} className="btn">
{text}
</button>
);
};
Props Validation with PropTypes
import PropTypes from 'prop-types';
const UserCard = ({ name, age, email, isActive }) => {
return (
<div className={`user-card ${isActive ? 'active' : 'inactive'}`}>
<h3>{name}</h3>
<p>Age: {age}</p>
<p>Email: {email}</p>
<span className="status">{isActive ? 'Active' : 'Inactive'}</span>
</div>
);
};
// PropTypes validation
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
email: PropTypes.string.isRequired,
isActive: PropTypes.bool,
};
// Default props
UserCard.defaultProps = {
isActive: true,
};
Children Props
// Component that uses children prop
const Card = ({ title, children }) => {
return (
<div className="card">
<div className="card-header">
<h3>{title}</h3>
</div>
<div className="card-body">{children}</div>
</div>
);
};
// Using children prop
const App = () => {
return (
<div>
<Card title="User Information">
<p>This content is passed as children</p>
<button>Edit Profile</button>
</Card>
<Card title="Settings">
<form>
<input type="text" placeholder="Username" />
<input type="email" placeholder="Email" />
<button type="submit">Save</button>
</form>
</Card>
</div>
);
};
State Management with useState
State allows components to create and manage their own data. React provides the useState
hook for functional components.
Basic useState Usage
import React, { useState } from 'react';
const Counter = () => {
// Declare state variable with initial value
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
const reset = () => {
setCount(0);
};
return (
<div className="counter">
<h2>Count: {count}</h2>
<div className="counter-buttons">
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
</div>
);
};
State with Objects and Arrays
import React, { useState } from 'react';
const UserProfile = () => {
// State with object
const [user, setUser] = useState({
name: '',
email: '',
age: 0,
});
// State with array
const [hobbies, setHobbies] = useState([]);
const [newHobby, setNewHobby] = useState('');
const updateUser = (field, value) => {
setUser((prevUser) => ({
...prevUser,
[field]: value,
}));
};
const addHobby = () => {
if (newHobby.trim() !== '') {
setHobbies((prevHobbies) => [...prevHobbies, newHobby]);
setNewHobby('');
}
};
const removeHobby = (index) => {
setHobbies((prevHobbies) => prevHobbies.filter((_, i) => i !== index));
};
return (
<div className="user-profile">
<h2>User Profile</h2>
<div className="form-group">
<input
type="text"
placeholder="Name"
value={user.name}
onChange={(e) => updateUser('name', e.target.value)}
/>
<input
type="email"
placeholder="Email"
value={user.email}
onChange={(e) => updateUser('email', e.target.value)}
/>
<input
type="number"
placeholder="Age"
value={user.age}
onChange={(e) => updateUser('age', parseInt(e.target.value))}
/>
</div>
<div className="hobbies-section">
<h3>Hobbies</h3>
<div className="add-hobby">
<input
type="text"
placeholder="Add a hobby"
value={newHobby}
onChange={(e) => setNewHobby(e.target.value)}
/>
<button onClick={addHobby}>Add</button>
</div>
<ul className="hobbies-list">
{hobbies.map((hobby, index) => (
<li key={index}>
{hobby}
<button onClick={() => removeHobby(index)}>Remove</button>
</li>
))}
</ul>
</div>
<div className="user-preview">
<h3>Preview</h3>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Age: {user.age}</p>
<p>Hobbies: {hobbies.join(', ')}</p>
</div>
</div>
);
};
Conditional State Updates
import React, { useState } from 'react';
const LoginForm = () => {
const [formData, setFormData] = useState({
username: '',
password: '',
rememberMe: false,
});
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: type === 'checkbox' ? checked : value,
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setError('');
try {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 2000));
if (formData.username === 'admin' && formData.password === 'password') {
alert('Login successful!');
} else {
setError('Invalid username or password');
}
} catch (err) {
setError('An error occurred during login');
} finally {
setIsLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="login-form">
<h2>Login</h2>
{error && <div className="error-message">{error}</div>}
<div className="form-group">
<input
type="text"
name="username"
placeholder="Username"
value={formData.username}
onChange={handleInputChange}
required
/>
</div>
<div className="form-group">
<input
type="password"
name="password"
placeholder="Password"
value={formData.password}
onChange={handleInputChange}
required
/>
</div>
<div className="form-group">
<label>
<input
type="checkbox"
name="rememberMe"
checked={formData.rememberMe}
onChange={handleInputChange}
/>
Remember me
</label>
</div>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Logging in...' : 'Login'}
</button>
</form>
);
};
Event Handling
React uses SyntheticEvents to handle events in a consistent way across different browsers.
Basic Event Handling
import React, { useState } from 'react';
const EventExamples = () => {
const [message, setMessage] = useState('');
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
// Click event handler
const handleClick = (e) => {
console.log('Button clicked!', e);
setMessage('Button was clicked!');
};
// Input change handler
const handleInputChange = (e) => {
setMessage(e.target.value);
};
// Mouse move handler
const handleMouseMove = (e) => {
setMousePosition({
x: e.clientX,
y: e.clientY,
});
};
// Form submit handler
const handleSubmit = (e) => {
e.preventDefault(); // Prevent page reload
alert(`Form submitted with message: ${message}`);
};
// Key press handler
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
alert('Enter key pressed!');
}
};
return (
<div className="event-examples" onMouseMove={handleMouseMove}>
<h2>Event Handling Examples</h2>
<p>
Mouse Position: X: {mousePosition.x}, Y: {mousePosition.y}
</p>
<form onSubmit={handleSubmit}>
<input
type="text"
value={message}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
placeholder="Type something..."
/>
<button type="submit">Submit</button>
</form>
<button onClick={handleClick}>Click me</button>
<p>Current message: {message}</p>
</div>
);
};
Event Handler Patterns
import React, { useState } from 'react';
const AdvancedEventHandling = () => {
const [items, setItems] = useState([
{ id: 1, name: 'Item 1', completed: false },
{ id: 2, name: 'Item 2', completed: true },
{ id: 3, name: 'Item 3', completed: false },
]);
// Handler with parameters
const toggleItem = (id) => {
setItems((prevItems) =>
prevItems.map((item) =>
item.id === id ? { ...item, completed: !item.completed } : item
)
);
};
// Handler for dynamic events
const handleItemAction = (id, action) => {
switch (action) {
case 'toggle':
toggleItem(id);
break;
case 'delete':
setItems((prevItems) => prevItems.filter((item) => item.id !== id));
break;
default:
console.log('Unknown action:', action);
}
};
// Inline handler with arrow function
const addItem = () => {
const newItem = {
id: Date.now(),
name: `Item ${items.length + 1}`,
completed: false,
};
setItems((prevItems) => [...prevItems, newItem]);
};
return (
<div className="advanced-events">
<h2>Advanced Event Handling</h2>
<button onClick={addItem}>Add Item</button>
<ul className="items-list">
{items.map((item) => (
<li key={item.id} className={item.completed ? 'completed' : ''}>
<span>{item.name}</span>
<div className="item-actions">
<button onClick={() => handleItemAction(item.id, 'toggle')}>
{item.completed ? 'Undo' : 'Complete'}
</button>
<button
onClick={() => handleItemAction(item.id, 'delete')}
className="delete-btn"
>
Delete
</button>
</div>
</li>
))}
</ul>
</div>
);
};
Component Communication
Parent to Child Communication
// Parent component
const ParentComponent = () => {
const [userTheme, setUserTheme] = useState('light');
const [userData, setUserData] = useState({
name: 'John Doe',
email: 'john@example.com',
});
return (
<div className={`app ${userTheme}`}>
<ChildComponent
theme={userTheme}
user={userData}
onThemeChange={setUserTheme}
/>
</div>
);
};
// Child component
const ChildComponent = ({ theme, user, onThemeChange }) => {
const toggleTheme = () => {
onThemeChange(theme === 'light' ? 'dark' : 'light');
};
return (
<div className="child-component">
<h2>Hello, {user.name}!</h2>
<p>Current theme: {theme}</p>
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'dark' : 'light'} theme
</button>
</div>
);
};
Child to Parent Communication
import React, { useState } from 'react';
// Child component that sends data to parent
const ContactForm = ({ onSubmit }) => {
const [formData, setFormData] = useState({
name: '',
email: '',
message: '',
});
const handleSubmit = (e) => {
e.preventDefault();
// Send data to parent
onSubmit(formData);
// Reset form
setFormData({ name: '', email: '', message: '' });
};
const handleChange = (e) => {
setFormData((prev) => ({
...prev,
[e.target.name]: e.target.value,
}));
};
return (
<form onSubmit={handleSubmit} className="contact-form">
<h3>Contact Form</h3>
<input
type="text"
name="name"
placeholder="Your Name"
value={formData.name}
onChange={handleChange}
required
/>
<input
type="email"
name="email"
placeholder="Your Email"
value={formData.email}
onChange={handleChange}
required
/>
<textarea
name="message"
placeholder="Your Message"
value={formData.message}
onChange={handleChange}
required
/>
<button type="submit">Send Message</button>
</form>
);
};
// Parent component that receives data from child
const ContactPage = () => {
const [submissions, setSubmissions] = useState([]);
const handleFormSubmit = (formData) => {
const newSubmission = {
...formData,
id: Date.now(),
timestamp: new Date().toLocaleString(),
};
setSubmissions((prev) => [...prev, newSubmission]);
alert('Message sent successfully!');
};
return (
<div className="contact-page">
<h2>Contact Us</h2>
<ContactForm onSubmit={handleFormSubmit} />
<div className="submissions">
<h3>Recent Submissions</h3>
{submissions.map((submission) => (
<div key={submission.id} className="submission">
<p>
<strong>From:</strong> {submission.name} ({submission.email})
</p>
<p>
<strong>Message:</strong> {submission.message}
</p>
<p>
<small>Sent: {submission.timestamp}</small>
</p>
</div>
))}
</div>
</div>
);
};
Complete Example: Todo Application
Let's build a complete todo application that demonstrates all the concepts we've learned:
import React, { useState } from 'react';
// Individual Todo Item Component
const TodoItem = ({ todo, onToggle, onDelete, onEdit }) => {
const [isEditing, setIsEditing] = useState(false);
const [editText, setEditText] = useState(todo.text);
const handleSave = () => {
if (editText.trim()) {
onEdit(todo.id, editText.trim());
setIsEditing(false);
}
};
const handleCancel = () => {
setEditText(todo.text);
setIsEditing(false);
};
return (
<li className={`todo-item ${todo.completed ? 'completed' : ''}`}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
{isEditing ? (
<div className="edit-mode">
<input
type="text"
value={editText}
onChange={(e) => setEditText(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSave()}
/>
<button onClick={handleSave}>Save</button>
<button onClick={handleCancel}>Cancel</button>
</div>
) : (
<div className="view-mode">
<span className="todo-text">{todo.text}</span>
<div className="todo-actions">
<button onClick={() => setIsEditing(true)}>Edit</button>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</div>
</div>
)}
</li>
);
};
// Add Todo Form Component
const AddTodoForm = ({ onAdd }) => {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
onAdd(text.trim());
setText('');
}
};
return (
<form onSubmit={handleSubmit} className="add-todo-form">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="What needs to be done?"
autoFocus
/>
<button type="submit">Add Todo</button>
</form>
);
};
// Filter Buttons Component
const FilterButtons = ({ filter, onFilterChange }) => {
const filters = [
{ key: 'all', label: 'All' },
{ key: 'active', label: 'Active' },
{ key: 'completed', label: 'Completed' },
];
return (
<div className="filter-buttons">
{filters.map((f) => (
<button
key={f.key}
className={filter === f.key ? 'active' : ''}
onClick={() => onFilterChange(f.key)}
>
{f.label}
</button>
))}
</div>
);
};
// Main Todo Application Component
const TodoApp = () => {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React fundamentals', completed: false },
{ id: 2, text: 'Build a todo app', completed: false },
{ id: 3, text: 'Master React hooks', completed: false },
]);
const [filter, setFilter] = useState('all');
// Add new todo
const addTodo = (text) => {
const newTodo = {
id: Date.now(),
text,
completed: false,
};
setTodos((prevTodos) => [...prevTodos, newTodo]);
};
// Toggle todo completion
const toggleTodo = (id) => {
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
// Delete todo
const deleteTodo = (id) => {
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
};
// Edit todo
const editTodo = (id, newText) => {
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id ? { ...todo, text: newText } : todo
)
);
};
// Filter todos based on current filter
const filteredTodos = todos.filter((todo) => {
switch (filter) {
case 'active':
return !todo.completed;
case 'completed':
return todo.completed;
default:
return true;
}
});
// Calculate statistics
const totalTodos = todos.length;
const completedTodos = todos.filter((todo) => todo.completed).length;
const activeTodos = totalTodos - completedTodos;
return (
<div className="todo-app">
<header className="todo-header">
<h1>Todo Application</h1>
<div className="todo-stats">
<span>Total: {totalTodos}</span>
<span>Active: {activeTodos}</span>
<span>Completed: {completedTodos}</span>
</div>
</header>
<AddTodoForm onAdd={addTodo} />
<FilterButtons filter={filter} onFilterChange={setFilter} />
<ul className="todo-list">
{filteredTodos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
onEdit={editTodo}
/>
))}
</ul>
{filteredTodos.length === 0 && (
<div className="empty-state">
<p>No todos found for the current filter.</p>
</div>
)}
</div>
);
};
export default TodoApp;
Best Practices and Common Patterns
Component Organization
// 1. Keep components small and focused
const Button = ({ children, variant = 'primary', onClick, ...props }) => {
return (
<button className={`btn btn-${variant}`} onClick={onClick} {...props}>
{children}
</button>
);
};
// 2. Use composition over inheritance
const Modal = ({ isOpen, onClose, title, children }) => {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<h2>{title}</h2>
<button onClick={onClose}>×</button>
</div>
<div className="modal-body">{children}</div>
</div>
</div>
);
};
// 3. Extract reusable logic into custom hooks
const useToggle = (initialValue = false) => {
const [value, setValue] = useState(initialValue);
const toggle = () => setValue((prev) => !prev);
const setTrue = () => setValue(true);
const setFalse = () => setValue(false);
return [value, toggle, setTrue, setFalse];
};
// Using the custom hook
const Example = () => {
const [isVisible, toggleVisible, show, hide] = useToggle(false);
return (
<div>
<button onClick={toggleVisible}>{isVisible ? 'Hide' : 'Show'}</button>
{isVisible && <p>This content is toggleable!</p>}
</div>
);
};
Performance Considerations
import React, { useState, useCallback, useMemo } from 'react';
const OptimizedComponent = ({ items, category }) => {
const [search, setSearch] = useState('');
// Memoize expensive calculations
const filteredItems = useMemo(() => {
return items.filter(
(item) =>
item.category === category &&
item.name.toLowerCase().includes(search.toLowerCase())
);
}, [items, category, search]);
// Memoize event handlers
const handleSearchChange = useCallback((e) => {
setSearch(e.target.value);
}, []);
return (
<div>
<input
type="text"
value={search}
onChange={handleSearchChange}
placeholder="Search items..."
/>
<ul>
{filteredItems.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};
Conclusion
React fundamentals form the foundation of modern web development with React. Understanding components, props, state, and event handling is essential for building interactive user interfaces.
Key Takeaways
- Components: Build reusable, modular UI pieces
- Props: Pass data from parent to child components
- State: Manage dynamic data within components
- Events: Handle user interactions and system events
- JSX: Write HTML-like syntax in JavaScript
- Composition: Combine components to build complex UIs
Next Steps
Now that you understand React fundamentals, you're ready to explore:
- React Hooks: Deep dive into useEffect, useContext, and custom hooks
- State Management: Learn about Redux, Context API, and other state management solutions
- React Router: Build single-page applications with client-side routing
- Performance Optimization: Learn about React.memo, useMemo, and useCallback
- Testing: Write tests for your React components
React's declarative nature and component-based architecture make it an excellent choice for building modern web applications. Practice these fundamentals, and you'll be well on your way to mastering React development!