Clarvo Blog·Web Development

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.

Clarvo
Clarvo Editorial Team
13 min read·21 Sept 2026·917 words
Master React Step by Step: From Components to Hooks and Real Projects
Source image via the referenced Medium article ↗

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

1. Create a React application with Vite

Create the projectbash
npm create vite@latest mini-market -- --template react
cd mini-market
npm install
npm run dev

Vite 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.

src/App.jsxjsx
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

src/components/ProductCard.jsxjsx
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 ideaThink of it asExample
ComponentReusable UI functionProductCard
PropInput from parentproduct, onAdd
StateChanging memory owned by UIcart count
EventSomething the user didbutton click
EffectSync with something outside Reactfetching an API

3. Render data with map and stable keys

Product listjsx
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

State in App.jsxjsx
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

Controlled formjsx
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

Empty cartjsx
{cart.length === 0 ? (
  <p>Your cart is empty.</p>
) : (
  <CartItems items={cart} />
)}

7. Filtering without unnecessary effects

Search productsjsx
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

Small fetch examplejsx
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.

movies.jsjsx
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' },
];
MovieCard.jsxjsx
export default function MovieCard({ movie, onSelect }) {
  return (
    <button className="movieCard" onClick={() => onSelect(movie)}>
      <img src={movie.poster} alt="" />
      <span>{movie.title}</span>
    </button>
  );
}
Catalogue.jsxjsx
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

UseGood choice?
One component needs a value from its parentProps
Several nearby components need the same stateLift state to their nearest common parent
Many distant components need stable app-level dataContext may help
Complex server state/cacheConsider a dedicated data library only when the problem exists

12. Build and deploy

Production buildbash
npm run build
npm run preview

Continue 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.

  1. Source inspiration — A Step-by-Step Guide to Mastering Reactmedium.com