React Mini E-Ticaret Projesi
Merhaba arkadaşlar, bu yazımda sizlere adım adım mini bir e-ticaret projesi nasıl yapılır onu anlatmaya çalışacağım. Bu projeyi yaparken Tanstack Query, Axios ve Zustand paketlerinin kullanımına da giriş yapmış olacağız. Faydalı bir rehber olmasını umuyorum. Hadi başlayalım.
Ben VSCode uygulamasını kullanarak geliştirme yapıyorum. Siz isterseniz farklı bir editör kullanabilirsiniz.
Proje Kurulumu
Vite ile projemizi oluşturalım:
npm create vite@latest
Proje ismimizi mini-e-commerce olarak girdikten sonra React ve TypeScript seçeneklerini seçerek ilerliyoruz.
Proje dizinine terminalde girmek için:
cd mini-e-commerce
(Siz farklı isim verdiyseniz o isme ait klasöre cd komutu ile girmelisiniz.)
Projemizde gereken paketleri yüklemek için:
npm install
Projemizin klasörünü vscode penceresinde açmak için:
code .
Eğer terminalimiz kapandıysa Ctrl+J kombinasyonuna basarak terminalimizi yeniden açalım ve React projemizde gereken paketleri yüklemek için bu komutu terminale yazalım:
npm install @tanstack/react-query axios zustand
Projemize TailwindCSS ekleyeceğiz, aşağıdaki komutları teker teker terminale girin:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js dosyasının içindekileri silin ve bu kodları yapıştırın:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
src klasöründeki index.css içindeki kodları silin ve bunu yapıştırın:
@tailwind base;
@tailwind components;
@tailwind utilities;
TailwindCSS kurulumunu başarıyla tamamladınız, tebrik ederim.
Uygulamamızda TanStack Query paketini kullanabilmek için main.tsx dosyasında tanımlamamız ve App’i (uygulamamızı) QueryClientProvider ile sarmamız gerekiyor. Bunun için src klasöründeki main.tsx dosyasını aşağıdaki gibi düzenleyelim:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
createRoot(document.getElementById("root")!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>
);
Tanstack Query paketi artık uygulamamızda kullanılmaya hazır.
Componentlerin Oluşturulması
src klasörüne sağ tıklayıp components ismiyle yeni bir klasör oluşturalım.
components klasörü içinde Cart.tsx isminde bir dosya oluşturalım, bu bizim ürün sepeti Component’imiz olacak. Dosyamızı oluşturduktan sonra açıp içine aşağıdaki kodları yazalım:
export default function Cart() {
return (
<div>
<h2 className="text-xl font-semibold">Cart</h2>
<p>Your cart is empty.</p>
</div>
);
}
Componentimiz sadece h2 etiketiyle başlık döndürüyor. Burada kullandığımız TailwindCSS’te bulunan utility class’lar yazımıza büyüklük ve kalınlık veriyor. Aynı şekilde components klasörü içinde List.tsx isminde bir dosya daha oluşturalım ve aşağıdaki kodları yazalım:
export default function List() {
return (
<div>
<h2 className="text-xl font-semibold">Product List</h2>
</div>
);
}
Bu da tamam, artık App component’imizde yeni oluşturduğumuz componentleri import edip kullanabiliriz. src klasöründeki App.tsx içinde var olan kodları silelim ve bu kodları yazalım:
import Cart from "./components/Cart";
import List from "./components/List";
export default function App() {
return (
<div className="container mx-auto">
<h1 className="text-3xl font-bold">E-Commerce App</h1>
<Cart />
<List />
</div>
);
}
App componentimizin genel yapısı oluştu. container ve mx-auto sınıfları sayfa içeriğinin ortalanmasını ve sayfamızın responsive olmasını sağlar. Uygulamamızı terminale bu komutu girerek çalıştıralım ve sonucu tarayıcımızda http://localhost:5173 adresinde görelim:
npm run dev
Ürünleri Servisten Fetch Etmek
Product modelimizi oluşturalım. src klasörü içinde models isminde yeni bir klasör oluşturalım, daha sonra models klasörü içinde Product.ts diye bir dosya oluşturalım ve içine bu kodları yazalım:
export default interface Product {
id: number;
title: string;
price: number;
description: string;
category: string;
image: string;
rating: {
rate: number;
count: number;
};
quantity?: number;
}
Ürünlerimizi fakestoreapi.com API’si üzerinden çekeceğiz bunun için önce Axios dosyamızı oluşturalım. src klasörü içinde api.ts adında bir dosya oluşturun ve içine bu kodları yazın:
import axios from "axios";
import Product from "./models/Product";
const api = axios.create({
baseURL: "https://fakestoreapi.com",
});
export const getCategories = async (): Promise<string[]> => {
const response = await api.get<string[]>("/products/categories");
return response.data;
};
export const getProducts = async (): Promise<Product[]> => {
const response = await api.get<Product[]>("/products");
return response.data;
};
export const getProductById = async (id: number): Promise<Product> => {
const response = await api.get<Product>(`/products/${id}`);
return response.data;
};
Burada api adında bir axios instance (örneği) oluşturduk. Onun üzerinden fetch (veri çekme) işlemlerimizi yönettik. Ayrıca tip güvenli bir yapı oluşturduk. TypeScript kullanmanın avantajlarından faydalandık.
Zustand ile Sepet Durumunu Yönetmek
Şimdi Zustand kullanarak sepet için kullanacağımız store’u hazırlayalım, src klasörü içinde store isminde bir klasör oluşturalım ve bunun içinde useCart.ts isminde bir dosya oluşturalım, bu kodları yazalım:
import { create } from "zustand";
import Product from "../models/Product";
interface CartState {
products: Product[];
add: (product: Product) => void;
remove: (id: number) => void;
increaseQuantity: (id: number) => void;
decreaseQuantity: (id: number) => void;
}
export const useCart = create<CartState>((set) => ({
products: [],
add: (product: Product) =>
set((state) => {
const existingProduct = state.products.find(
(item) => item.id === product.id
);
if (existingProduct) {
return {
...state,
products: state.products.map((item) =>
item.id === product.id
? { ...item, quantity: (item.quantity || 0) + 1 }
: item
),
};
} else {
return {
...state,
products: [...state.products, { ...product, quantity: 1 }],
};
}
}),
remove: (id: number) =>
set((state) => ({
...state,
products: state.products.filter((product) => product.id !== id),
})),
increaseQuantity: (id: number) =>
set((state) => ({
...state,
products: state.products.map((product) =>
product.id === id
? { ...product, quantity: (product.quantity || 0) + 1 }
: product
),
})),
decreaseQuantity: (id: number) =>
set((state) => ({
...state,
products: state.products
.map((product) =>
product.id === id
? { ...product, quantity: (product.quantity || 0) - 1 }
: product
)
.filter((product) => product.quantity && product.quantity > 0),
})),
}));
Zustand ile sepetimizi oluşturduk. Ürün ekleme, silme ürün adedini arttırma ve azaltma fonksiyonlarımızı yazdık. Burada set fonksiyonu state’i değiştiren bir reducer fonksiyondur. Reducer fonksiyonlar pure fonksiyonlardır. Daha önce Redux veya Context API ile çalıştıysanız reducer kavramına aşinasınızdır. Zustand React store yönetimi için yeni ve modern bir çözüm. Dökümanını okumanızı tavsiye ederim.
Ürün Listesi ve Sepeti Tamamlamak
Artık ürünlerimizi API üzerinden fetch etmeye ve sepetimizi kullanmaya hazırız. List.tsx componentimize bu kodları yazalım:
import { useQuery } from "@tanstack/react-query";
import Product from "../models/Product";
import { getProducts } from "../api";
import { useCart } from "../store/useCart";
export default function List() {
const { isLoading, isError, data, error } = useQuery<Product[]>({
queryKey: ["products"],
queryFn: getProducts,
});
const { add } = useCart();
if (isError) {
return <div>Error: {error.message}</div>;
}
if (isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<h2 className="text-xl font-semibold">Product List</h2>
<ul className="list-disc">
{data?.map((product) => (
<li key={product.id} className="mb-1">
<span>{product.title}</span>
<button
className="bg-blue-500 hover:bg-blue-700 text-white px-1 mx-1 rounded"
onClick={() => add(product)}
>
Add to cart
</button>
</li>
))}
</ul>
</div>
);
}
Burada ürünleri Tanstack Query kullanarak fetch ettik. Tanstack Query bizim için loading ve error durumlarını da yönetiyor. Eğer bir hata oluşursa ekrana hata mesajı yazılacak. Yüklenmesini beklerken de ekrana Loading… yazılacak. Ayrıca sepetimize ürün ekleme butonu da ekledik.
Sepetteki ürünleri Zustand ile oluşturduğumuz store üzerinden listelemek ve güncellemek için Cart componentimizi de değiştirmemiz gerekiyor, Cart.tsx dosyasına bu kodları yazalım:
import { useCart } from "../store/useCart";
export default function Cart() {
const { products, increaseQuantity, decreaseQuantity, remove } = useCart();
return (
<div>
<h2 className="text-xl font-semibold">Cart</h2>
{products.length === 0 && <p>Your cart is empty.</p>}
<ul className="list-disc">
{products.map((product) => (
<li key={product.id} className="mb-1">
<span>{product.title} </span>
<span>(Quantity: {product.quantity}) </span>
<button
className="bg-green-500 hover:bg-green-700 text-white px-1 mx-1 rounded"
onClick={() => increaseQuantity(product.id)}
>
Increase Qty.
</button>
<button
className="bg-red-500 hover:bg-red-700 text-white px-1 mx-1 rounded"
onClick={() => decreaseQuantity(product.id)}
>
Decrease Qty.
</button>
<button
className="bg-blue-500 hover:bg-blue-700 text-white px-1 mx-1 rounded"
onClick={() => remove(product.id)}
>
Remove
</button>
</li>
))}
</ul>
</div>
);
}
Cart sayfamızda artık sepetteki ürünler listeleniyor, ayrıca ürünlerin yanındaki butonlarla adedi arttırma, azaltma veya sepetten silme işlemlerini yapabiliyoruz.
Teşekkür
Rehberimiz burada sona eriyor, okuduğunuz için teşekkür ederim. Dilerseniz projenin kodlarına ve canlı demosuna aşağıdaki linklerden ulaşabilirsiniz.
https://github.com/aygunbyr/mini-e-commerce
https://mini-e-commerce-medium.vercel.app/
İyi çalışmalar dilerim!