Przejdź do głównej zawartości

02. Sklep internetowy

Zbudujesz aplikację sklepu internetowego z listą produktów, filtrowaniem, widokiem szczegółów produktu i koszykiem zakupowym. Dane produktów mogą pochodzić z publicznego API (FakeStore API) lub z lokalnego pliku JSON.

Czego się nauczycie?

  • Pobieranie danych z zewnętrznego API (fetch + useEffect)
  • Zarządzanie stanem koszyka za pomocą useReducer lub Context API
  • Routing z parametrami URL (np. /product/5)
  • Filtrowanie i sortowanie list w React
  • Praca zespołowa — jeden robi listing + filtrowanie, drugi koszyk + szczegóły

W prawdziwej firmie...

E-commerce to jeden z największych segmentów webdevu. Umiejętność zbudowania koszyka zakupowego, zarządzania stanem produktów i integracji z API to absolutna podstawa każdego Front-End developera.

Umiejętności rynkowe

  • Zarządzanie globalnym stanem (koszyk)
  • Routing z parametrami
  • Filtrowanie i sortowanie danych
  • Integracja z REST API
  1. Lista produktów — pobrana z API lub z pliku JSON, wyświetlona jako karty/siatka

  2. Filtrowanie — po kategorii, cenie, nazwie (wyszukiwarka)

  3. Szczegóły produktu — osobna strona z pełnym opisem, galerią i opcją dodania do koszyka

  4. Koszyk — dodawanie, usuwanie, zmiana ilości, podsumowanie ceny

  5. Podsumowanie zamówienia — widok z listą produktów i całkowitą ceną

Strona główna / Listing

Siatka produktów z miniaturami, nazwą, ceną i kategorią. Filtr kategorii i sortowanie po cenie.

Szczegóły produktu

Pełny opis, większe zdjęcie, ocena, cena, przycisk “Dodaj do koszyka”. URL: /product/:id.

Koszyk

Lista dodanych produktów z możliwością zmiany ilości i usuwania. Suma całkowita. Przycisk “Zamów”.

{
"items": [
{
"id": 1,
"title": "Fjallraven - Foldsack No. 1 Backpack",
"price": 109.95,
"image": "https://fakestoreapi.com/img/81fAn.jpg",
"quantity": 2
}
],
"totalItems": 2,
"totalPrice": 219.90
}

Minimalne wymagania:

  • Lista produktów z danych lokalnych (plik mockData.js)
  • Filtrowanie po kategorii (select)
  • Klikając produkt — widok szczegółów (osobna strona)
  • Dodawanie do koszyka (przycisk “Dodaj”)
  • Koszyk — lista dodanych produktów + suma
  • Routing: /, /product/:id, /cart
  • Foldersrc/
    • Folderdata/
      • products.js
    • Foldercomponents/
      • ProductCard.jsx
      • CartItem.jsx
      • FilterBar.jsx
    • Folderpages/
      • HomePage.jsx
      • ProductPage.jsx
      • CartPage.jsx
    • Foldercontext/
      • CartContext.jsx
Ocena: 3.0

Scenariusz 1: Zakupy

  1. Użytkownik otwiera sklep — widzi siatkę produktów
  2. Filtruje kategorię “electronics”
  3. Klika na produkt — przechodzi do /product/3
  4. Czyta opis, klika “Dodaj do koszyka”
  5. Przechodzi do koszyka — widzi produkt, zmienia ilość na 2
  6. Widzi zaktualizowaną cenę

Scenariusz 2: Wyszukiwanie

  1. Użytkownik wpisuje “jacket” w wyszukiwarce
  2. Lista produktów filtruje się na żywo
  3. Wybiera jeden produkt i dodaje do koszyka
src/context/CartContext.jsx
import { createContext, useContext, useReducer, useEffect } from 'react';
const CartContext = createContext(null);
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM': {
const existing = state.items.find((i) => i.id === action.product.id);
if (existing) {
return {
...state,
items: state.items.map((i) =>
i.id === action.product.id ? { ...i, quantity: i.quantity + 1 } : i
),
};
}
return { ...state, items: [...state.items, { ...action.product, quantity: 1 }] };
}
case 'REMOVE_ITEM':
return { ...state, items: state.items.filter((i) => i.id !== action.id) };
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map((i) =>
i.id === action.id ? { ...i, quantity: Math.max(1, action.quantity) } : i
),
};
case 'CLEAR_CART':
return { ...state, items: [] };
default:
return state;
}
}
export function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, { items: [] }, (init) => {
const saved = localStorage.getItem('cart');
return saved ? JSON.parse(saved) : init;
});
useEffect(() => {
localStorage.setItem('cart', JSON.stringify(state));
}, [state]);
const totalItems = state.items.reduce((sum, i) => sum + i.quantity, 0);
const totalPrice = state.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
return (
<CartContext.Provider value={{ ...state, totalItems, totalPrice, dispatch }}>
{children}
</CartContext.Provider>
);
}
export function useCart() {
return useContext(CartContext);
}
src/hooks/useProducts.js
import { useState, useEffect } from 'react';
function useProducts() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://fakestoreapi.com/products')
.then((res) => res.json())
.then((data) => setProducts(data))
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);
return { products, loading, error };
}
export default useProducts;

Powodzenia!

Sklep internetowy to jeden z najpopularniejszych projektów portfolio. Dobrze zrobiony koszyk z Context API + useReducer pokazuje, że rozumiecie zarządzanie globalnym stanem — a to jest pytane na prawie każdej rozmowie kwalifikacyjnej!