Building Your First React App: A Story-Driven Tutorial for Beginners
Learn React from scratch in 2025 with this beginner React tutorial: components, props, state, hooks, and building a real app step by step.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
Building Your First React App: A Story-Driven Tutorial for Beginners
My first React component was a disaster. I had copied a tutorial, gotten it working, and immediately broke it trying to add one more feature. I didn't understand what the code actually meant — I was pattern-matching from examples without the underlying mental model.
If I could go back and give myself one piece of advice, it would be this: React is simpler than it looks. The complexity is in the ecosystem, not the core idea.
React's core idea: your UI is a function of your data. Change the data, the UI updates. That's it.
In this tutorial, you'll learn React from scratch by building a real task manager app. By the end, you'll understand components, props, state, events, and hooks — the foundation for everything else in React.
What You'll Build
A functional task manager application with:
- Adding and displaying tasks
- Marking tasks complete
- Filtering by status
- Deleting tasks
Simple enough to understand, real enough to use.
Prerequisites
Before starting:
- Basic JavaScript: variables, functions, arrays, objects
- Arrow functions and destructuring
map()andfilter()array methods
If any of those are unfamiliar, spend time on our modern JavaScript guide first.
Setup: Create Your React Project
npm create vite@latest task-manager -- --template react
cd task-manager
npm install
npm run dev
Open http://localhost:5173. You'll see the default Vite React page. We'll replace all of it.
Open the project in your editor. The important files:
src/main.jsx— entry point, renders the appsrc/App.jsx— your main component (we'll start here)
Core Concept 1: Components
A React component is a JavaScript function that returns JSX — HTML-like syntax that React transforms into real DOM elements.
Clear out src/App.jsx and start fresh:
function App() {
return (
<div>
<h1>My Task Manager</h1>
<p>Let's get organized.</p>
</div>
);
}
export default App;
This is a component. A function. It returns what should appear on screen.
Your First Custom Component
Create src/components/TaskItem.jsx:
function TaskItem({ task }) {
return (
<div className="task-item">
<span>{task.text}</span>
</div>
);
}
export default TaskItem;
Notice { task } — this is destructuring props. When you use <TaskItem task={...} />, React passes an object of all attributes as the first argument. We destructure task out of it.
Core Concept 2: Props
Props let you pass data from parent to child. They make components reusable.
// Parent (App.jsx)
import TaskItem from './components/TaskItem';
function App() {
const task = { id: 1, text: 'Learn React', done: false };
return (
<div>
<h1>My Task Manager</h1>
<TaskItem task={task} />
</div>
);
}
Props flow one direction: down from parent to child. A child cannot directly modify its parent's data.
Core Concept 3: State with useState
State is data that lives inside a component and can change. When state changes, the component re-renders.
import { useState } from 'react';
function App() {
// useState returns [currentValue, setterFunction]
const [tasks, setTasks] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Build something', done: false },
]);
return (
<div>
<h1>Tasks ({tasks.length})</h1>
{tasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
</div>
);
}
Why key? When React renders a list, it needs a unique identifier for each item to track changes efficiently. Always provide a key prop when mapping arrays.
Core Concept 4: Event Handling
Let's add the ability to mark tasks complete:
function App() {
const [tasks, setTasks] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Build something', done: false },
]);
function toggleTask(id) {
// Never mutate state directly — create a new array
setTasks(tasks.map(task =>
task.id === id ? { ...task, done: !task.done } : task
));
}
return (
<div>
<h1>Tasks</h1>
{tasks.map(task => (
<TaskItem
key={task.id}
task={task}
onToggle={toggleTask}
/>
))}
</div>
);
}
Update TaskItem to use the onToggle prop:
function TaskItem({ task, onToggle }) {
return (
<div
className={`task-item ${task.done ? 'done' : ''}`}
onClick={() => onToggle(task.id)}
>
<input
type="checkbox"
checked={task.done}
onChange={() => onToggle(task.id)}
/>
<span style={{ textDecoration: task.done ? 'line-through' : 'none' }}>
{task.text}
</span>
</div>
);
}
Core Concept 5: Adding Items — Controlled Inputs
A controlled input's value is driven by state:
function AddTaskForm({ onAdd }) {
const [text, setText] = useState('');
function handleSubmit(e) {
e.preventDefault(); // Prevent page reload
if (!text.trim()) return;
onAdd(text.trim());
setText(''); // Clear input after adding
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a task..."
/>
<button type="submit">Add</button>
</form>
);
}
Back in App.jsx:
function App() {
const [tasks, setTasks] = useState([]);
function addTask(text) {
const newTask = {
id: Date.now(), // Simple unique ID
text,
done: false,
};
setTasks([...tasks, newTask]);
}
function toggleTask(id) {
setTasks(tasks.map(t =>
t.id === id ? { ...t, done: !t.done } : t
));
}
function deleteTask(id) {
setTasks(tasks.filter(t => t.id !== id));
}
return (
<div>
<h1>Task Manager</h1>
<AddTaskForm onAdd={addTask} />
{tasks.map(task => (
<TaskItem
key={task.id}
task={task}
onToggle={toggleTask}
onDelete={deleteTask}
/>
))}
</div>
);
}
Core Concept 6: useEffect for Side Effects
useEffect runs code in response to component mounting or state changes:
import { useState, useEffect } from 'react';
function App() {
const [tasks, setTasks] = useState(() => {
// Load from localStorage on initial render
const saved = localStorage.getItem('tasks');
return saved ? JSON.parse(saved) : [];
});
// Save to localStorage whenever tasks changes
useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(tasks));
}, [tasks]); // [tasks] is the dependency array
// ... rest of component
}
The dependency array [tasks] tells React: "run this effect when tasks changes." An empty array [] means "run once on mount." No array means "run after every render."
Filtering Tasks
Add filter state and computed values:
function App() {
const [tasks, setTasks] = useState([]);
const [filter, setFilter] = useState('all'); // 'all', 'active', 'done'
const filteredTasks = tasks.filter(task => {
if (filter === 'active') return !task.done;
if (filter === 'done') return task.done;
return true;
});
return (
<div>
<h1>Task Manager</h1>
<AddTaskForm onAdd={addTask} />
<div className="filters">
{['all', 'active', 'done'].map(f => (
<button
key={f}
onClick={() => setFilter(f)}
className={filter === f ? 'active' : ''}
>
{f}
</button>
))}
</div>
{filteredTasks.map(task => (
<TaskItem key={task.id} task={task} onToggle={toggleTask} onDelete={deleteTask} />
))}
<p>{tasks.filter(t => !t.done).length} tasks remaining</p>
</div>
);
}
What You Just Learned
You've covered the fundamentals that power every React application:
- Components — reusable functions that return JSX
- Props — data passed from parent to child
- useState — state that triggers re-renders when changed
- Event handlers — responding to user input
- Controlled inputs — form inputs driven by state
- useEffect — side effects like localStorage and API calls
- Derived state — computing values from existing state (filtering)
These seven concepts are the foundation of everything else in React.
What to Learn Next
With React fundamentals solid, the next steps are:
- React Router — navigation between pages
- React Query or SWR — data fetching from APIs
- Context API — sharing state across components without prop drilling
For the next step in your React journey, our React hooks tutorial covers all the hooks beyond useState and useEffect. When you're ready for a real framework, our React vs Next.js vs Remix comparison helps you choose the right one. And to understand state as your app grows, check out our React state management 2025 guide.
Frequently Asked Questions
How long does it take to learn React as a beginner?
2–4 weeks for fundamentals with daily practice. A real project takes another month. Full ecosystem comfort takes 3–6 months. Build things — don't just watch tutorials.
Do I need to know JavaScript before learning React?
Yes. Arrow functions, destructuring, array methods (map/filter), and Promises are required knowledge before React makes sense.
What is JSX in React?
JSX is HTML-like syntax inside JavaScript. It compiles to React.createElement() calls. Use curly braces {} to embed JavaScript expressions.
What is the difference between props and state?
Props come from outside (parent) and are read-only. State lives inside the component and can change. Change state to trigger re-renders.
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
How to Deploy a React App to Vercel in 10 Minutes
Deploy a React app to Vercel in 10 minutes: from npm create vite to live URL, custom domain setup, environment variables, and preview deployments.
GraphQL vs REST: Which API Style Should You Learn in 2025?
GraphQL vs REST API compared honestly for 2025: when each makes sense, real code examples, and which API style to learn first as a developer.
JavaScript Promises and Async/Await: Finally Understand Them
JavaScript async await and Promises explained clearly: the event loop, Promise chains, async/await patterns, error handling, and common mistakes to avoid.
How to Pass a JavaScript Interview at Google, Meta, or Amazon
How to pass a JavaScript interview at top tech companies: closures, event loop, promises, DOM questions, system design, and real interview questions answered.