Master React Step by Step: From Components to Hooks and Real Projects
A beginner path through JSX, components, props, state, hooks, styling, lists, conditional rendering and project practice.

0. React does not replace JavaScript
Before React, be comfortable with functions, arrays, objects, map/filter, destructuring, modules and basic async JavaScript. React’s official Quick Start focuses on components, JSX, conditions, lists, events and sharing data; those concepts are the backbone of this course.
If JavaScript still feels difficult
Free fundamentals and exercises.
Open resource ↗scratch.mit.eduScratchVisual programming for absolute beginners who want to practise logic first.
Open resource ↗react.devReact official LearnCurrent React documentation and interactive examples.
Open resource ↗1. Create a React application with Vite
npm create vite@latest mini-market -- --template react
cd mini-market
npm install
npm run devVite is a modern development tool. The important files are src/main.jsx, which mounts the app, and src/App.jsx, where you can begin composing your UI.
export default function App() {
return (
<main>
<h1>Mini Market</h1>
<p>Student-friendly products in one place.</p>
</main>
);
}2. Components: break the screen into pieces
export default function ProductCard({ product, onAdd }) {
return (
<article className="productCard">
<img src={product.image} alt={product.name} />
<h2>{product.name}</h2>
<p>{product.price.toLocaleString()} FCFA</p>
<button onClick={() => onAdd(product)}>Add to cart</button>
</article>
);
}| React idea | Think of it as | Example |
|---|---|---|
| Component | Reusable UI function | ProductCard |
| Prop | Input from parent | product, onAdd |
| State | Changing memory owned by UI | cart count |
| Event | Something the user did | button click |
| Effect | Sync with something outside React | fetching an API |
3. Render data with map and stable keys
const products = [
{ id: 1, name: 'Backpack', price: 12000, image: '/bag.jpg' },
{ id: 2, name: 'Calculator', price: 8500, image: '/calculator.jpg' },
];
function ProductGrid({ onAdd }) {
return (
<section className="grid">
{products.map((product) => (
<ProductCard key={product.id} product={product} onAdd={onAdd} />
))}
</section>
);
}4. State: make the cart actually change
import { useState } from 'react';
import ProductGrid from './components/ProductGrid';
export default function App() {
const [cart, setCart] = useState([]);
function addToCart(product) {
setCart((current) => [...current, product]);
}
return (
<main>
<header><h1>Mini Market</h1><strong>Cart: {cart.length}</strong></header>
<ProductGrid onAdd={addToCart} />
</main>
);
}5. Forms: add products from user input
import { useState } from 'react';
export default function ProductForm({ onCreate }) {
const [name, setName] = useState('');
const [price, setPrice] = useState('');
function submit(event) {
event.preventDefault();
if (!name.trim() || Number(price) <= 0) return;
onCreate({ id: Date.now(), name: name.trim(), price: Number(price) });
setName('');
setPrice('');
}
return (
<form onSubmit={submit}>
<input value={name} onChange={(e) => setName(e.target.value)} placeholder="Product name" />
<input value={price} onChange={(e) => setPrice(e.target.value)} inputMode="numeric" placeholder="Price" />
<button>Add product</button>
</form>
);
}6. Conditional rendering
{cart.length === 0 ? (
<p>Your cart is empty.</p>
) : (
<CartItems items={cart} />
)}7. Filtering without unnecessary effects
const [query, setQuery] = useState('');
const visibleProducts = products.filter((product) =>
product.name.toLowerCase().includes(query.trim().toLowerCase())
);visibleProducts is derived from existing state, so calculate it during rendering. You do not need useEffect plus another state variable just to keep a filtered copy in sync.
8. Fetch API data with a clear loading/error state
import { useEffect, useState } from 'react';
export default function Users() {
const [users, setUsers] = useState([]);
const [status, setStatus] = useState('loading');
useEffect(() => {
const controller = new AbortController();
fetch('https://jsonplaceholder.typicode.com/users', { signal: controller.signal })
.then((response) => {
if (!response.ok) throw new Error('Request failed');
return response.json();
})
.then((data) => { setUsers(data); setStatus('ready'); })
.catch((error) => {
if (error.name !== 'AbortError') setStatus('error');
});
return () => controller.abort();
}, []);
if (status === 'loading') return <p>Loading…</p>;
if (status === 'error') return <p>Could not load users.</p>;
return users.map((user) => <p key={user.id}>{user.name}</p>);
}9. Project 1 checkpoint — Mini Market
- ✓Product list comes from an array or API.
- ✓ProductCard receives data through props.
- ✓Cart count uses state.
- ✓Search is derived from query state.
- ✓Add-product form validates input.
- ✓UI remains usable on a narrow phone screen.
10. Project 2 — build a Netflix-style catalogue
The goal is not to copy Netflix branding. Use the familiar catalogue pattern to practise reusable rows, poster cards and filtering. Start with local data so you learn React before adding API keys.
export const movies = [
{ id: 1, title: 'Aurion', genre: 'Adventure', poster: '/posters/aurion.jpg' },
{ id: 2, title: 'The Algorithm', genre: 'Technology', poster: '/posters/algorithm.jpg' },
{ id: 3, title: 'Savanna', genre: 'Documentary', poster: '/posters/savanna.jpg' },
];export default function MovieCard({ movie, onSelect }) {
return (
<button className="movieCard" onClick={() => onSelect(movie)}>
<img src={movie.poster} alt="" />
<span>{movie.title}</span>
</button>
);
}import { useMemo, useState } from 'react';
import { movies } from './movies';
import MovieCard from './MovieCard';
export default function Catalogue() {
const [genre, setGenre] = useState('All');
const [selected, setSelected] = useState(null);
const visible = useMemo(
() => genre === 'All' ? movies : movies.filter((movie) => movie.genre === genre),
[genre]
);
return (
<main>
<select value={genre} onChange={(e) => setGenre(e.target.value)}>
<option>All</option><option>Adventure</option><option>Technology</option><option>Documentary</option>
</select>
<section className="movieGrid">
{visible.map((movie) => <MovieCard key={movie.id} movie={movie} onSelect={setSelected} />)}
</section>
{selected ? <p>Selected: {selected.title}</p> : null}
</main>
);
}11. When Context becomes useful
| Use | Good choice? |
|---|---|
| One component needs a value from its parent | Props |
| Several nearby components need the same state | Lift state to their nearest common parent |
| Many distant components need stable app-level data | Context may help |
| Complex server state/cache | Consider a dedicated data library only when the problem exists |
12. Build and deploy
npm run build
npm run previewContinue learning
Sources and further reading
Clarvo articles are independently written. These references let you inspect the original inspiration and primary or official sources used to deepen the guide.
