React is the world's most popular JavaScript library for building user interfaces — used by Facebook, Netflix, Airbnb, and millions of apps worldwide. This tutorial takes you from zero to building real React applications, step by step.
What you'll learn
| Topic | What you'll be able to do |
|---|---|
| Setup | Create a React app and run it locally |
| JSX | Write HTML-like code inside JavaScript |
| Components | Build reusable UI building blocks |
| Props | Pass data between components |
| State | Make your UI interactive |
| Hooks | Use React's most important built-in hooks |
| Events | Handle clicks, inputs, and form submissions |
| Lists & Keys | Render dynamic data |
| Conditional rendering | Show/hide UI based on state |
| Projects | Build 3 real React apps |
React version used: React 19 (latest stable as of 2025)
Prerequisites: Basic JavaScript knowledge (variables, functions, arrays, objects). If you're new to JavaScript, read JavaScript Tutorial for Beginners first.
Part 1 — Why React?
| Feature | Why it matters |
|---|---|
| Component-based | Build complex UIs from small, reusable pieces |
| Declarative | Describe what the UI should look like — React handles the DOM |
| Huge ecosystem | React Router, Redux, Next.js, thousands of libraries |
| Job market | Most in-demand frontend skill — ~150k+ job listings |
| Performance | Virtual DOM minimizes expensive real DOM updates |
| Flexible | Web, mobile (React Native), desktop, static sites |
| Meta-maintained | Backed by Meta, used at Facebook, Instagram, WhatsApp |
React vs other frameworks
| React | Vue | Angular | |
|---|---|---|---|
| Type | Library | Framework | Full framework |
| Learning curve | Medium | Low | High |
| Syntax | JSX | Templates | TypeScript + decorators |
| Flexibility | High | Medium | Low (opinionated) |
| Job market | Highest | Medium | Medium |
| Best for | SPAs, large apps | Beginners, small–medium | Enterprise |
Part 2 — Setup
2.1 Prerequisites
You need Node.js installed. Check:
node --version # should be v18+
npm --version # comes with Node
If not installed, download from nodejs.org (choose LTS).
2.2 Create your first React app
The fastest way to start is Vite (faster than Create React App):
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
Open http://localhost:5173 — you'll see the React starter page.
Alternative: Create React App (classic, slower):
npx create-react-app my-react-app
cd my-react-app
npm start
2.3 Project structure
my-react-app/
├── public/ # Static files (index.html)
├── src/
│ ├── App.jsx # Root component
│ ├── main.jsx # Entry point (mounts App)
│ └── index.css # Global styles
├── package.json
└── vite.config.js
2.4 Recommended editor
Use VS Code with these extensions:
- ES7+ React/Redux/React-Native snippets — shortcuts like
rfce→ full component - Prettier — auto-formatting
- ESLint — catches errors
Part 3 — JSX
JSX lets you write HTML-like syntax inside JavaScript. It's not HTML — it's syntactic sugar that React compiles to React.createElement() calls.
// JSX
const element = <h1>Hello, World!</h1>;
// What it compiles to (you never write this manually)
const element = React.createElement('h1', null, 'Hello, World!');
JSX rules
1. Return a single root element — wrap multiple elements in a <div> or <> (Fragment):
// Wrong ❌
return (
<h1>Title</h1>
<p>Paragraph</p>
);
// Correct ✅ — use Fragment
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
2. Close all tags — even self-closing ones:
<img src="photo.jpg" alt="Photo" /> // ✅
<input type="text" /> // ✅
<br /> // ✅
3. Use className not class (because class is a reserved JS keyword):
<div className="container">...</div> // ✅
<div class="container">...</div> // ❌
4. Use {} for JavaScript expressions:
const name = "Alice";
const age = 25;
return (
<p>Name: {name}, Age: {age}</p>
);
// Renders: Name: Alice, Age: 25
5. Inline styles use objects with camelCase properties:
const style = { backgroundColor: 'blue', fontSize: '16px' };
<div style={style}>Styled!</div>
// Or inline:
<div style={{ color: 'red', fontWeight: 'bold' }}>Red text</div>
JSX cheat sheet:
| JSX | HTML equivalent |
|---|---|
className |
class |
htmlFor |
for |
onClick |
onclick |
onChange |
onchange |
tabIndex |
tabindex |
{expression} |
N/A — embeds JS |
{/* comment */} |
<!-- comment --> |
Part 4 — Components
Components are the building blocks of React apps. A component is a JavaScript function that returns JSX.
4.1 Function components
// The simplest component
function Greeting() {
return <h1>Hello, World!</h1>;
}
// Use it like an HTML tag
function App() {
return (
<div>
<Greeting />
<Greeting />
<Greeting />
</div>
);
}
Rules for components:
- Name must start with capital letter (
Greeting, notgreeting) - Must return JSX (or
nullto render nothing) - Can be in same file or separate files
4.2 Component in its own file
// src/components/Greeting.jsx
function Greeting() {
return <h1>Hello, World!</h1>;
}
export default Greeting;
// src/App.jsx
import Greeting from './components/Greeting';
function App() {
return (
<div>
<Greeting />
</div>
);
}
export default App;
4.3 Arrow function components
Both styles work identically:
// Function declaration
function Button() {
return <button>Click me</button>;
}
// Arrow function (also common)
const Button = () => {
return <button>Click me</button>;
};
// Short arrow (implicit return for single expression)
const Button = () => <button>Click me</button>;
Part 5 — Props
Props (short for "properties") let you pass data into components — like function arguments.
5.1 Basic props
// Define a component that accepts props
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Pass props when using the component
function App() {
return (
<div>
<Greeting name="Alice" />
<Greeting name="Bob" />
<Greeting name="Charlie" />
</div>
);
}
// Renders: Hello, Alice! Hello, Bob! Hello, Charlie!
5.2 Multiple props
function UserCard({ name, age, role }) {
return (
<div className="card">
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Role: {role}</p>
</div>
);
}
function App() {
return (
<UserCard name="Alice" age={28} role="Developer" />
);
}
Note: strings use "quotes", everything else (numbers, booleans, objects, arrays) uses {curly braces}.
5.3 Default props
function Button({ label = "Click me", color = "blue" }) {
return (
<button style={{ backgroundColor: color }}>
{label}
</button>
);
}
// Uses defaults
<Button />
// Overrides defaults
<Button label="Submit" color="green" />
5.4 children prop
children is a special prop that receives everything between opening and closing tags:
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
function App() {
return (
<Card>
<h2>Title</h2>
<p>Any content goes here.</p>
</Card>
);
}
5.5 Props are read-only
Never modify props inside a component. Props flow one way — from parent to child.
// ❌ Wrong — never mutate props
function Bad({ count }) {
count = count + 1; // Don't do this
return <p>{count}</p>;
}
// ✅ Correct — use the value, don't change it
function Good({ count }) {
return <p>{count}</p>;
}
Part 6 — State and useState
State is data that changes over time and triggers a re-render when it changes.
6.1 useState hook
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // initial value = 0
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
How useState works:
const [value, setter] = useState(initialValue);
value— current state valuesetter— function to update state (triggers re-render)initialValue— starting value (only used on first render)
6.2 State with strings
import { useState } from 'react';
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>
);
}
6.3 State with booleans (toggle)
import { useState } from 'react';
function Toggle() {
const [isOn, setIsOn] = useState(false);
return (
<div>
<p>Light is {isOn ? 'ON' : 'OFF'}</p>
<button onClick={() => setIsOn(!isOn)}>
Toggle
</button>
</div>
);
}
6.4 State with objects
import { useState } from 'react';
function UserForm() {
const [user, setUser] = useState({ name: '', email: '' });
const handleChange = (e) => {
// Spread existing object, update only changed field
setUser({ ...user, [e.target.name]: e.target.value });
};
return (
<form>
<input
name="name"
value={user.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="email"
value={user.email}
onChange={handleChange}
placeholder="Email"
/>
<p>Name: {user.name}, Email: {user.email}</p>
</form>
);
}
6.5 State with arrays
import { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState(['Buy milk', 'Walk dog']);
const [input, setInput] = useState('');
const addTodo = () => {
if (!input.trim()) return;
setTodos([...todos, input]); // create new array — don't push()
setInput('');
};
const removeTodo = (index) => {
setTodos(todos.filter((_, i) => i !== index));
};
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="New todo"
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => removeTodo(index)}>×</button>
</li>
))}
</ul>
</div>
);
}
State rules:
- Never mutate state directly — always create a new value
setTodos([...todos, newItem])✅ — creates new arraytodos.push(newItem)thensetTodos(todos)❌ — mutates, won't re-render
Part 7 — Event Handling
React uses camelCase event names and passes event handler functions:
// ❌ HTML-style (doesn't work in React)
<button onclick="doSomething()">Click</button>
// ✅ React-style
<button onClick={doSomething}>Click</button>
<button onClick={() => doSomething()}>Click</button>
7.1 Common events
function EventExamples() {
return (
<div>
{/* Click */}
<button onClick={() => console.log('clicked')}>Click</button>
{/* Input change */}
<input onChange={(e) => console.log(e.target.value)} />
{/* Form submit */}
<form onSubmit={(e) => {
e.preventDefault(); // Prevent page reload
console.log('submitted');
}}>
<button type="submit">Submit</button>
</form>
{/* Mouse events */}
<div
onMouseEnter={() => console.log('mouse in')}
onMouseLeave={() => console.log('mouse out')}
>
Hover me
</div>
{/* Keyboard events */}
<input
onKeyDown={(e) => {
if (e.key === 'Enter') console.log('Enter pressed');
}}
/>
</div>
);
}
7.2 Event object
function InputTracker() {
const handleChange = (e) => {
console.log(e.target.value); // current input value
console.log(e.target.name); // input name attribute
console.log(e.target.type); // "text", "checkbox", etc.
console.log(e.target.checked); // for checkboxes
};
return <input name="username" onChange={handleChange} />;
}
Common React events
| Event | Trigger |
|---|---|
onClick |
Mouse click |
onChange |
Input value changes |
onSubmit |
Form submitted |
onKeyDown / onKeyUp |
Key pressed/released |
onFocus / onBlur |
Element focused/unfocused |
onMouseEnter / onMouseLeave |
Mouse hover |
onScroll |
Element scrolled |
onLoad |
Element loaded |
Part 8 — Conditional Rendering
Show or hide UI based on state or props.
8.1 if statement
function UserPanel({ isLoggedIn }) {
if (!isLoggedIn) {
return <p>Please log in.</p>;
}
return <p>Welcome back!</p>;
}
8.2 Ternary operator (most common)
function Status({ isActive }) {
return (
<span className={isActive ? 'green' : 'red'}>
{isActive ? 'Active' : 'Inactive'}
</span>
);
}
8.3 && (short-circuit) for optional content
function Notification({ message }) {
return (
<div>
<h1>Dashboard</h1>
{message && <p className="alert">{message}</p>}
</div>
);
}
// If message is empty string / null / undefined, nothing renders
8.4 Early return for complex conditions
function LoadingState({ loading, error, data }) {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
if (!data) return <p>No data</p>;
return <div>{data.map(item => <p key={item.id}>{item.name}</p>)}</div>;
}
Part 9 — Lists and Keys
Render arrays of data with .map().
const fruits = ['Apple', 'Banana', 'Cherry'];
function FruitList() {
return (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
);
}
9.1 Keys — why they matter
React uses keys to track which items changed, added, or removed. Keys must be unique among siblings.
// ❌ No key — React warns and performance suffers
{items.map(item => <li>{item.name}</li>)}
// ✅ Stable ID from data (best)
{items.map(item => <li key={item.id}>{item.name}</li>)}
// ✅ Index (acceptable if list doesn't reorder)
{items.map((item, index) => <li key={index}>{item.name}</li>)}
9.2 Rendering objects
const users = [
{ id: 1, name: 'Alice', role: 'Admin' },
{ id: 2, name: 'Bob', role: 'User' },
{ id: 3, name: 'Charlie', role: 'User' },
];
function UserTable() {
return (
<table>
<thead>
<tr><th>Name</th><th>Role</th></tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.role}</td>
</tr>
))}
</tbody>
</table>
);
}
Part 10 — useEffect Hook
useEffect runs side effects — fetching data, subscribing to events, updating the document title, etc.
import { useState, useEffect } from 'react';
useEffect(() => {
// side effect code here
return () => {
// cleanup (optional)
};
}, [dependencies]);
10.1 Dependency array controls when effect runs
// Run once on mount (empty array)
useEffect(() => {
console.log('Component mounted');
}, []);
// Run on every render (no array — usually not what you want)
useEffect(() => {
console.log('Every render');
});
// Run when 'count' changes
useEffect(() => {
console.log('Count changed to:', count);
}, [count]);
10.2 Fetching data
import { useState, useEffect } from 'react';
function Posts() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts?_limit=5')
.then(res => res.json())
.then(data => {
setPosts(data);
setLoading(false);
});
}, []); // fetch once on mount
if (loading) return <p>Loading...</p>;
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
10.3 Cleanup
Return a cleanup function to prevent memory leaks:
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(interval); // cleanup on unmount
}, []);
Part 11 — Other Essential Hooks
11.1 useRef
Ref holds a mutable value that persists across renders without causing re-renders. Most common use: access DOM elements.
import { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus input</button>
</div>
);
}
11.2 useMemo
Memoizes an expensive calculation — only re-runs when dependencies change:
import { useState, useMemo } from 'react';
function ExpensiveList({ items }) {
const [filter, setFilter] = useState('');
const filteredItems = useMemo(() => {
// Only re-calculates when items or filter changes
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]);
return (
<>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>
</>
);
}
11.3 useCallback
Memoizes a function — prevents unnecessary re-renders of child components:
import { useState, useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback, new function created every render → Child re-renders
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // never changes — empty dependency array
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment parent</button>
<Child onClick={handleClick} />
</>
);
}
Hooks summary
| Hook | Purpose | When to use |
|---|---|---|
useState |
Component state | Any data that changes |
useEffect |
Side effects | Fetch, timers, subscriptions |
useRef |
DOM access / mutable value | Focus, animations, non-re-render values |
useMemo |
Memoize computed value | Expensive calculations |
useCallback |
Memoize function | Prevent child re-renders |
useContext |
Read context | Avoid prop drilling |
useReducer |
Complex state logic | Multi-step state updates |
Part 12 — Forms
12.1 Controlled form (recommended)
React controls the input value via state — the "single source of truth":
import { useState } from 'react';
function LoginForm() {
const [formData, setFormData] = useState({
email: '',
password: '',
});
const [error, setError] = useState('');
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (!formData.email || !formData.password) {
setError('All fields are required');
return;
}
setError('');
console.log('Submitting:', formData);
// call your API here
};
return (
<form onSubmit={handleSubmit}>
{error && <p style={{ color: 'red' }}>{error}</p>}
<div>
<label>Email</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
</div>
<div>
<label>Password</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
</div>
<button type="submit">Log In</button>
</form>
);
}
12.2 Checkbox and select
function Preferences() {
const [agreed, setAgreed] = useState(false);
const [country, setCountry] = useState('us');
return (
<form>
{/* Checkbox */}
<label>
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
I agree to the terms
</label>
{/* Select */}
<select value={country} onChange={(e) => setCountry(e.target.value)}>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
</select>
<p>Country: {country}, Agreed: {String(agreed)}</p>
</form>
);
}
Part 13 — Component Composition Patterns
13.1 Lifting state up
When two siblings need to share state, move it to their common parent:
import { useState } from 'react';
// Parent owns the state
function TemperatureConverter() {
const [celsius, setCelsius] = useState(0);
const fahrenheit = (celsius * 9/5) + 32;
return (
<div>
<CelsiusInput value={celsius} onChange={setCelsius} />
<p>{celsius}°C = {fahrenheit.toFixed(1)}°F</p>
</div>
);
}
// Children receive state via props
function CelsiusInput({ value, onChange }) {
return (
<input
type="number"
value={value}
onChange={(e) => onChange(Number(e.target.value))}
/>
);
}
13.2 useContext — avoid prop drilling
When you need to share data deeply (e.g. current user, theme, language):
import { createContext, useContext, useState } from 'react';
// 1. Create context
const ThemeContext = createContext('light');
// 2. Provide context at top level
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<Main />
</ThemeContext.Provider>
);
}
// 3. Consume context anywhere in the tree
function Header() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<header style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle theme
</button>
</header>
);
}
Part 14 — Project 1: Counter App
A classic beginner project — multiple counters with add/remove:
import { useState } from 'react';
function Counter({ id, onRemove }) {
const [count, setCount] = useState(0);
return (
<div style={{ border: '1px solid #ccc', padding: '16px', margin: '8px' }}>
<h3>Counter #{id}</h3>
<p style={{ fontSize: '2rem' }}>{count}</p>
<button onClick={() => setCount(count - 1)}>−</button>
<button onClick={() => setCount(0)} style={{ margin: '0 8px' }}>Reset</button>
<button onClick={() => setCount(count + 1)}>+</button>
<br />
<button onClick={onRemove} style={{ color: 'red', marginTop: '8px' }}>
Remove
</button>
</div>
);
}
function App() {
const [counters, setCounters] = useState([1]);
const [nextId, setNextId] = useState(2);
const addCounter = () => {
setCounters([...counters, nextId]);
setNextId(nextId + 1);
};
const removeCounter = (id) => {
setCounters(counters.filter(c => c !== id));
};
return (
<div style={{ padding: '20px' }}>
<h1>Counter App</h1>
<button onClick={addCounter}>Add Counter</button>
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{counters.map(id => (
<Counter
key={id}
id={id}
onRemove={() => removeCounter(id)}
/>
))}
</div>
</div>
);
}
export default App;
What you practised: useState, arrays in state, lifting state, props, event handling, conditional rendering.
Part 15 — Project 2: Todo App
import { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', done: false },
{ id: 2, text: 'Build a project', done: false },
]);
const [input, setInput] = useState('');
const [filter, setFilter] = useState('all'); // all | active | done
const addTodo = (e) => {
e.preventDefault();
if (!input.trim()) return;
setTodos([
...todos,
{ id: Date.now(), text: input.trim(), done: false }
]);
setInput('');
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.done;
if (filter === 'done') return todo.done;
return true;
});
const remaining = todos.filter(t => !t.done).length;
return (
<div style={{ maxWidth: '500px', margin: '40px auto', fontFamily: 'sans-serif' }}>
<h1>Todo App</h1>
<form onSubmit={addTodo} style={{ display: 'flex', gap: '8px' }}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="What needs to be done?"
style={{ flex: 1, padding: '8px' }}
/>
<button type="submit">Add</button>
</form>
<div style={{ margin: '12px 0', display: 'flex', gap: '8px' }}>
{['all', 'active', 'done'].map(f => (
<button
key={f}
onClick={() => setFilter(f)}
style={{ fontWeight: filter === f ? 'bold' : 'normal' }}
>
{f}
</button>
))}
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{filteredTodos.map(todo => (
<li key={todo.id} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '8px 0' }}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none', flex: 1 }}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>×</button>
</li>
))}
</ul>
<p>{remaining} item{remaining !== 1 ? 's' : ''} left</p>
</div>
);
}
export default TodoApp;
What you practised: complex state, array manipulation (map/filter), forms, conditional rendering, filtering.
Part 16 — Project 3: Weather App (with API)
Fetches real weather data from a public API:
import { useState } from 'react';
function WeatherApp() {
const [city, setCity] = useState('');
const [weather, setWeather] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const fetchWeather = async (e) => {
e.preventDefault();
if (!city.trim()) return;
setLoading(true);
setError('');
setWeather(null);
try {
// Free API — no key needed
const geoRes = await fetch(
`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`
);
const geoData = await geoRes.json();
if (!geoData.results?.length) {
setError('City not found');
return;
}
const { latitude, longitude, name, country } = geoData.results[0];
const weatherRes = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,wind_speed_10m,weathercode`
);
const weatherData = await weatherRes.json();
setWeather({
name,
country,
temp: weatherData.current.temperature_2m,
wind: weatherData.current.wind_speed_10m,
code: weatherData.current.weathercode,
});
} catch {
setError('Failed to fetch weather. Try again.');
} finally {
setLoading(false);
}
};
const getCondition = (code) => {
if (code === 0) return '☀️ Clear sky';
if (code <= 3) return '🌤️ Partly cloudy';
if (code <= 48) return '🌫️ Foggy';
if (code <= 67) return '🌧️ Rain';
if (code <= 77) return '❄️ Snow';
if (code <= 82) return '🌦️ Showers';
return '⛈️ Thunderstorm';
};
return (
<div style={{ maxWidth: '400px', margin: '40px auto', fontFamily: 'sans-serif', textAlign: 'center' }}>
<h1>Weather App</h1>
<form onSubmit={fetchWeather} style={{ display: 'flex', gap: '8px' }}>
<input
value={city}
onChange={(e) => setCity(e.target.value)}
placeholder="Enter city name..."
style={{ flex: 1, padding: '8px' }}
/>
<button type="submit" disabled={loading}>
{loading ? '...' : 'Search'}
</button>
</form>
{error && <p style={{ color: 'red', marginTop: '16px' }}>{error}</p>}
{weather && (
<div style={{ marginTop: '24px', padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h2>{weather.name}, {weather.country}</h2>
<p style={{ fontSize: '3rem', margin: '8px 0' }}>{weather.temp}°C</p>
<p>{getCondition(weather.code)}</p>
<p>Wind: {weather.wind} km/h</p>
</div>
)}
</div>
);
}
export default WeatherApp;
What you practised: useEffect vs fetch-on-demand, async/await, loading/error states, API integration.
Learning path
| Stage | Focus | Time |
|---|---|---|
| Beginner | JSX, components, props, useState, events | 1–2 weeks |
| Intermediate | useEffect, forms, lists, conditional rendering | 2–3 weeks |
| Advanced basics | useContext, custom hooks, data fetching | 2–3 weeks |
| React ecosystem | React Router, Zustand/Redux, React Query | 3–4 weeks |
| Full-stack | Next.js (SSR/SSG), TypeScript + React | 4–6 weeks |
| Production | Testing (Jest + RTL), CI/CD, performance | Ongoing |
After this tutorial, learn next:
- React Router — client-side routing, multiple pages
- TypeScript + React — type safety for larger projects
- React Query / TanStack Query — server state, caching, data fetching
- Next.js — SSR, SSG, file-based routing, full-stack React
- Zustand or Redux Toolkit — global state management
- React Testing Library — component tests
React vs related terms
| Term | What it is |
|---|---|
| React | JavaScript library for building UI components |
| Next.js | React framework — adds routing, SSR, SSG, API routes |
| Vite | Build tool — faster alternative to Create React App |
| JSX | JavaScript syntax extension for writing HTML-like code |
| Virtual DOM | React's internal representation — diffs and updates real DOM efficiently |
| Hook | Function that lets you use React state/lifecycle in function components |
| Component | Reusable UI building block — a function that returns JSX |
| State | Data that changes over time and triggers UI updates |
| Props | Data passed from parent to child component |
| Redux | Popular global state management library, often paired with React |
Common mistakes
| Mistake | Fix |
|---|---|
Mutating state directly (state.items.push(x)) |
Create new array: setState([...state.items, x]) |
Missing key prop in lists |
Add unique key={item.id} to each mapped element |
| Calling hooks inside loops/conditions | Always call hooks at the top level of a component |
Forgetting e.preventDefault() in forms |
Add it to prevent page reload on submit |
Infinite loop in useEffect |
Check dependencies — setters in deps array cause loops |
| Stale closure in event handler | Use functional updater: setCount(c => c + 1) |
useEffect without cleanup |
Return cleanup function to prevent memory leaks |
class instead of className |
React uses className for HTML class attribute |
Frequently asked questions
Do I need to know JavaScript before learning React? Yes — learn JavaScript fundamentals first (variables, functions, arrays, objects, ES6+ features like arrow functions, destructuring, spread). The JavaScript Tutorial for Beginners covers everything you need.
React or Next.js — which should I learn first? React first. Next.js is built on top of React — you need React fundamentals before Next.js makes sense. Learn React for a few weeks, then move to Next.js.
Do I need to learn class components? Not if you're starting today. Function components with hooks are the modern standard (since 2019). You might encounter class components in legacy codebases — understanding them is useful but not a priority.
What is the best state management for React in 2025?
For local state: useState. For server state: TanStack Query (React Query). For global client state: Zustand (simple) or Redux Toolkit (complex apps). Don't reach for global state managers too early — useState + lifting state handles most apps.
React or Vue — which is better for beginners? Vue has a gentler learning curve. React is more widely used in industry (~3x more jobs). If your goal is employment, React is the better choice. If you want a faster start, Vue.
How long does it take to learn React? Basic components, props, and state: 1–2 weeks of consistent practice. Enough to build a simple app: 4–6 weeks. Job-ready: 3–6 months (including ecosystem tools, projects, TypeScript).