React.js for Beginners
What is React?
React is a JavaScript library for building user interfaces, created and maintained by Meta (formerly Facebook). It is the most widely-used frontend library in the world, powering applications like Facebook, Instagram, Netflix, Airbnb, and thousands of others.
React's core idea is simple but powerful: your UI is a function of your data. Instead of manually updating the DOM when data changes (as you would with vanilla JavaScript or jQuery), you describe what the UI should look like for any given state, and React figures out how to update the DOM efficiently. This is called declarative rendering.
React is built around three key concepts:
- Components — Reusable, self-contained building blocks for your UI
- State — Data that changes over time and triggers re-renders
- Props — Data passed from parent components to child components
Setting Up a React Project
The easiest way to start a new React project is with Vite, which is the currently recommended approach. It provides fast development builds and a modern development experience.
# Create a new React project with Vite
npm create vite@latest my-react-app -- --template react
# Navigate into the project
cd my-react-app
# Install dependencies
npm install
# Start the development server
npm run dev
This creates a project structure like this:
my-react-app/
node_modules/
public/
src/
App.css
App.jsx
index.css
main.jsx
index.html
package.json
vite.config.js
The entry point is src/main.jsx, which renders the root App component into the DOM. The .jsx extension indicates files that contain JSX syntax (more on that next).
Components: The Building Blocks
In React, everything is a component. A component is a reusable piece of UI that encapsulates its own structure, logic, and sometimes styling. Modern React uses functional components — regular JavaScript functions that return JSX.
// The simplest possible component
function Greeting() {
return <h1>Hello, World!</h1>;
}
// A component with more structure
function Header() {
return (
<header>
<h1>My Application</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
);
}
// Components can use other components
function App() {
return (
<div>
<Header />
<main>
<Greeting />
<p>Welcome to my React application.</p>
</main>
</div>
);
}
export default App;
Key rules for components:
- Component names must start with a capital letter (e.g.,
Header, notheader). React uses this to distinguish between HTML elements and custom components. - A component must return a single root element. If you need to return multiple elements without a wrapper div, use a Fragment:
<>...</>. - Components should be kept small and focused on a single responsibility.
JSX: HTML-like Syntax in JavaScript
JSX is a syntax extension that lets you write HTML-like code inside JavaScript. It is not HTML — it gets compiled into regular JavaScript function calls by Vite (via Babel or SWC) before reaching the browser.
// JSX lets you embed JavaScript expressions using curly braces
function UserProfile() {
const name = "Alice";
const age = 28;
const avatarUrl = "https://example.com/alice.jpg";
return (
<div className="profile">
<img src={avatarUrl} alt={`${name}'s avatar`} />
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Born in {2026 - age}</p>
</div>
);
}
// Important JSX differences from HTML:
// 1. Use className instead of class
<div className="container">...</div>
// 2. Use htmlFor instead of for
<label htmlFor="email">Email</label>
// 3. All tags must be closed (even self-closing ones)
<img src="photo.jpg" alt="Photo" />
<br />
<input type="text" />
// 4. Use camelCase for HTML attributes
<div tabIndex={0} onClick={handleClick}>...</div>
// 5. Inline styles use an object, not a string
<div style={{ color: "blue", fontSize: "16px", marginTop: "20px" }}>
Styled text
</div>
// 6. Fragments for multiple root elements
function Columns() {
return (
<>
<td>Column 1</td>
<td>Column 2</td>
<td>Column 3</td>
</>
);
}
{} let you embed any valid JavaScript expression: variables, function calls, ternary operators, array methods, and more. You cannot use statements like if/else or for loops directly inside JSX — use ternary operators or move the logic outside the return statement.
Props: Passing Data to Components
Props (short for "properties") are how you pass data from a parent component to a child component. Think of them like function arguments — they make components reusable by allowing them to display different data.
// Child component that accepts props
function UserCard({ name, role, avatarUrl }) {
return (
<div className="user-card">
<img src={avatarUrl} alt={`${name}'s avatar`} />
<h3>{name}</h3>
<p>{role}</p>
</div>
);
}
// Parent component passing props
function TeamPage() {
return (
<div>
<h1>Our Team</h1>
<UserCard
name="Alice"
role="Frontend Developer"
avatarUrl="/images/alice.jpg"
/>
<UserCard
name="Bob"
role="UX Designer"
avatarUrl="/images/bob.jpg"
/>
<UserCard
name="Charlie"
role="Backend Developer"
avatarUrl="/images/charlie.jpg"
/>
</div>
);
}
// Props with default values
function Button({ text = "Click me", variant = "primary", onClick }) {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{text}
</button>
);
}
// Using it
<Button text="Save" onClick={handleSave} />
<Button text="Cancel" variant="secondary" onClick={handleCancel} />
<Button /> {/* Uses defaults: "Click me", "primary" */}
// The children prop: passing content between opening and closing tags
function Card({ title, children }) {
return (
<div className="card">
<h3>{title}</h3>
<div className="card-body">
{children}
</div>
</div>
);
}
<Card title="Welcome">
<p>This paragraph is passed as the children prop.</p>
<button>Get Started</button>
</Card>
Props are read-only. A component should never modify its own props. If you need data that changes over time, you need state.
useState: Managing State
State is data that belongs to a component and can change over time. When state changes, React automatically re-renders the component (and its children) to reflect the new data. The useState hook is how you add state to a functional component.
import { useState } from "react";
// A simple counter
function Counter() {
// useState returns [currentValue, setterFunction]
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>
);
}
// State with a string
function NameInput() {
const [name, setName] = useState("");
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
<p>Hello, {name || "stranger"}!</p>
</div>
);
}
// State with a boolean (toggle)
function TogglePanel() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? "Hide" : "Show"} Details
</button>
{isOpen && (
<div className="panel">
<p>These are the hidden details.</p>
</div>
)}
</div>
);
}
// State with an object
function UserForm() {
const [formData, setFormData] = useState({
name: "",
email: "",
age: ""
});
function handleChange(e) {
const { name, value } = e.target;
// Always create a new object (never mutate state directly)
setFormData(prev => ({
...prev,
[name]: value
}));
}
return (
<form>
<input name="name" value={formData.name}
onChange={handleChange} placeholder="Name" />
<input name="email" value={formData.email}
onChange={handleChange} placeholder="Email" />
<input name="age" value={formData.age}
onChange={handleChange} placeholder="Age" />
</form>
);
}
// State with an array
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
function addTodo() {
if (input.trim()) {
setTodos([...todos, { id: Date.now(), text: input }]);
setInput("");
}
}
function removeTodo(id) {
setTodos(todos.filter(todo => todo.id !== id));
}
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)}
placeholder="Add a 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>
);
}
setTodos([...todos, newTodo]) instead of todos.push(newTodo). React detects changes by comparing references, so mutations will not trigger re-renders.
useEffect: Side Effects
The useEffect hook lets you perform side effects in your components. Side effects are operations that interact with the outside world: fetching data from an API, setting up event listeners, updating the document title, or running timers.
import { useState, useEffect } from "react";
// Run on every render (rarely what you want)
useEffect(() => {
console.log("Component rendered");
});
// Run only once when component mounts (empty dependency array)
useEffect(() => {
console.log("Component mounted");
}, []);
// Run when specific values change
useEffect(() => {
console.log(`Count changed to ${count}`);
}, [count]);
// Real-world example: fetching data from an API
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchUsers() {
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: fetch once on mount
if (loading) return <p>Loading users...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
);
}
// Cleanup function: runs when component unmounts or before re-running
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
// Cleanup: remove the listener when component unmounts
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return <p>Window width: {width}px</p>;
}
// Updating the document title based on state
function PageTitle({ title }) {
useEffect(() => {
document.title = title;
}, [title]);
return <h1>{title}</h1>;
}
Event Handling
React handles events similarly to DOM events, but with a few differences. Events in React use camelCase naming (e.g., onClick instead of onclick), and you pass functions as event handlers, not strings.
function EventExamples() {
// Click handler
function handleClick() {
alert("Button clicked!");
}
// Click handler with the event object
function handleButtonClick(e) {
console.log("Button text:", e.target.textContent);
}
// Passing arguments to handlers
function handleDelete(itemId) {
console.log("Deleting item:", itemId);
}
// Form submission
function handleSubmit(e) {
e.preventDefault(); // Prevent page reload
console.log("Form submitted!");
}
return (
<div>
{/* Basic click */}
<button onClick={handleClick}>Click me</button>
{/* With event object */}
<button onClick={handleButtonClick}>Log my text</button>
{/* Passing arguments (use an arrow function) */}
<button onClick={() => handleDelete(42)}>
Delete Item 42
</button>
{/* Form events */}
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Type something" />
<button type="submit">Submit</button>
</form>
{/* Other common events */}
<input
onChange={(e) => console.log(e.target.value)}
onFocus={() => console.log("Focused")}
onBlur={() => console.log("Blurred")}
/>
<div
onMouseEnter={() => console.log("Mouse entered")}
onMouseLeave={() => console.log("Mouse left")}
>
Hover over me
</div>
</div>
);
}
onClick={handleClick}, not onClick={handleClick()}. The second version calls the function immediately during rendering rather than when the button is clicked.
Conditional Rendering
In React, you can render different content based on conditions. Since JSX is just JavaScript, you can use any JavaScript expression to decide what to display.
function Dashboard({ user, notifications }) {
// Method 1: if/else (before the return)
if (!user) {
return <p>Please log in to view the dashboard.</p>;
}
// Method 2: Ternary operator (inside JSX)
return (
<div>
<h1>Welcome, {user.name}!</h1>
{/* Ternary: show one thing or another */}
{user.isAdmin ? (
<span className="badge">Admin</span>
) : (
<span className="badge">User</span>
)}
{/* Logical AND: show something or nothing */}
{notifications.length > 0 && (
<div className="notification-bar">
You have {notifications.length} new notifications.
</div>
)}
{/* Multiple conditions */}
{user.subscription === "premium" && (
<PremiumFeatures />
)}
{/* Rendering different components based on status */}
{user.status === "active" && <ActiveBadge />}
{user.status === "away" && <AwayBadge />}
{user.status === "offline" && <OfflineBadge />}
</div>
);
}
// Extracted component for complex conditions
function StatusMessage({ status }) {
const messages = {
loading: <p>Loading data...</p>,
error: <p className="error">Something went wrong.</p>,
empty: <p>No results found.</p>,
success: <p>Data loaded successfully!</p>
};
return messages[status] || null;
}
Lists and Keys
Rendering lists of data is one of the most common tasks in React. You use the .map() array method to transform data into JSX elements. Every item in a list needs a unique key prop so React can efficiently track changes.
// Simple list rendering
function FruitList() {
const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];
return (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
);
}
// List with objects (use a unique ID as key, not index)
function ProductList() {
const products = [
{ id: 1, name: "Laptop", price: 999 },
{ id: 2, name: "Mouse", price: 29 },
{ id: 3, name: "Keyboard", price: 79 },
{ id: 4, name: "Monitor", price: 449 }
];
return (
<div className="product-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<h3>{product.name}</h3>
<p>${product.price}</p>
<button>Add to Cart</button>
</div>
))}
</div>
);
}
// Filtering and rendering
function ActiveUsers({ users }) {
const activeUsers = users.filter(user => user.isActive);
return (
<div>
<h2>Active Users ({activeUsers.length})</h2>
{activeUsers.length === 0 ? (
<p>No active users right now.</p>
) : (
<ul>
{activeUsers.map(user => (
<li key={user.id}>
{user.name} — last seen {user.lastActive}
</li>
))}
</ul>
)}
</div>
);
}
// Rendering a list of components
function BlogPage({ posts }) {
return (
<div>
{posts.map(post => (
<ArticleCard
key={post.id}
title={post.title}
excerpt={post.excerpt}
author={post.author}
date={post.publishedAt}
/>
))}
</div>
);
}
key prop (like a database ID). Avoid using the array index as a key if items can be reordered, added, or removed — this can cause rendering bugs and break component state. The index is only safe as a key when the list is static and will never change.
Basic Styling Approaches
React supports several approaches to styling components. Here are the most common ones:
// ---- 1. CSS Files (traditional approach) ----
// styles.css
// .card { background: white; border-radius: 8px; padding: 16px; }
// .card-title { font-size: 1.25rem; font-weight: bold; }
import "./styles.css";
function Card() {
return (
<div className="card">
<h3 className="card-title">My Card</h3>
</div>
);
}
// ---- 2. CSS Modules (scoped styles) ----
// Card.module.css
// .card { background: white; border-radius: 8px; }
// .title { font-size: 1.25rem; }
import styles from "./Card.module.css";
function Card() {
return (
<div className={styles.card}>
<h3 className={styles.title}>My Card</h3>
</div>
);
}
// ---- 3. Inline Styles (JavaScript objects) ----
function Card() {
const cardStyle = {
background: "white",
borderRadius: "8px", // camelCase, not kebab-case
padding: "16px",
boxShadow: "0 2px 4px rgba(0,0,0,0.1)"
};
return (
<div style={cardStyle}>
<h3 style={{ fontSize: "1.25rem", fontWeight: "bold" }}>
My Card
</h3>
</div>
);
}
// ---- 4. Conditional class names ----
function Button({ variant, isDisabled }) {
// Manually building class strings
const className = `btn btn-${variant} ${isDisabled ? "disabled" : ""}`;
return <button className={className.trim()}>Click</button>;
}
// Or using the popular "clsx" library
// import clsx from "clsx";
// const className = clsx("btn", `btn-${variant}`, { disabled: isDisabled });
Complete Example: A Task Manager
Let's combine everything into a complete, working application. This task manager demonstrates components, props, state, events, conditional rendering, and list rendering.
import { useState } from "react";
// Individual task component
function TaskItem({ task, onToggle, onDelete }) {
return (
<li style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "12px",
borderBottom: "1px solid #eee"
}}>
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
<input
type="checkbox"
checked={task.completed}
onChange={() => onToggle(task.id)}
/>
<span style={{
textDecoration: task.completed ? "line-through" : "none",
color: task.completed ? "#999" : "#333"
}}>
{task.text}
</span>
</div>
<button
onClick={() => onDelete(task.id)}
style={{
background: "#e74c3c",
color: "white",
border: "none",
borderRadius: "4px",
padding: "4px 12px",
cursor: "pointer"
}}
>
Delete
</button>
</li>
);
}
// Stats component
function TaskStats({ tasks }) {
const total = tasks.length;
const completed = tasks.filter(t => t.completed).length;
const remaining = total - completed;
return (
<div style={{
display: "flex",
gap: "20px",
padding: "12px",
background: "#f8f9fa",
borderRadius: "8px",
marginBottom: "16px"
}}>
<span>Total: {total}</span>
<span style={{ color: "#27ae60" }}>Done: {completed}</span>
<span style={{ color: "#e67e22" }}>Remaining: {remaining}</span>
</div>
);
}
// Main App component
function TaskManager() {
const [tasks, setTasks] = useState([
{ id: 1, text: "Learn React basics", completed: true },
{ id: 2, text: "Build a project", completed: false },
{ id: 3, text: "Deploy to production", completed: false }
]);
const [newTask, setNewTask] = useState("");
const [filter, setFilter] = useState("all"); // all, active, completed
function addTask(e) {
e.preventDefault();
if (!newTask.trim()) return;
setTasks([
...tasks,
{ id: Date.now(), text: newTask.trim(), completed: false }
]);
setNewTask("");
}
function toggleTask(id) {
setTasks(tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
}
function deleteTask(id) {
setTasks(tasks.filter(task => task.id !== id));
}
// Filter tasks based on current filter
const filteredTasks = tasks.filter(task => {
if (filter === "active") return !task.completed;
if (filter === "completed") return task.completed;
return true;
});
return (
<div style={{ maxWidth: "600px", margin: "40px auto", padding: "20px" }}>
<h1>Task Manager</h1>
<TaskStats tasks={tasks} />
{/* Add task form */}
<form onSubmit={addTask} style={{ display: "flex", gap: "8px", marginBottom: "16px" }}>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="Add a new task..."
style={{ flex: 1, padding: "8px 12px", borderRadius: "4px", border: "1px solid #ddd" }}
/>
<button type="submit" style={{
background: "#3498db",
color: "white",
border: "none",
borderRadius: "4px",
padding: "8px 20px",
cursor: "pointer"
}}>
Add
</button>
</form>
{/* Filter buttons */}
<div style={{ display: "flex", gap: "8px", marginBottom: "16px" }}>
{["all", "active", "completed"].map(f => (
<button
key={f}
onClick={() => setFilter(f)}
style={{
padding: "6px 16px",
borderRadius: "4px",
border: "1px solid #ddd",
background: filter === f ? "#3498db" : "white",
color: filter === f ? "white" : "#333",
cursor: "pointer",
textTransform: "capitalize"
}}
>
{f}
</button>
))}
</div>
{/* Task list */}
{filteredTasks.length === 0 ? (
<p style={{ textAlign: "center", color: "#999", padding: "20px" }}>
No tasks to show.
</p>
) : (
<ul style={{ listStyle: "none", padding: 0, border: "1px solid #eee", borderRadius: "8px" }}>
{filteredTasks.map(task => (
<TaskItem
key={task.id}
task={task}
onToggle={toggleTask}
onDelete={deleteTask}
/>
))}
</ul>
)}
</div>
);
}
export default TaskManager;
Summary and Next Steps
Congratulations! You have learned the fundamental concepts of React:
- Components — Reusable functions that return JSX describing your UI
- JSX — HTML-like syntax with JavaScript expressions embedded via curly braces
- Props — Data passed from parent to child, making components configurable
- useState — Hook for managing local component state that triggers re-renders
- useEffect — Hook for side effects like data fetching, subscriptions, and DOM updates
- Event handling — Responding to user interactions with camelCase event props
- Conditional rendering — Showing different UI based on state and props
- Lists and keys — Rendering dynamic lists with unique keys for efficient updates
- Styling — CSS files, CSS Modules, inline styles, and conditional classes
From here, you can explore more advanced topics:
- React Router — Client-side navigation between pages
- Context API — Share state across components without prop drilling
- useReducer — Complex state management similar to Redux
- Custom hooks — Extract and reuse stateful logic
- Next.js — A full-stack React framework with routing, SSR, and more