React
مشروع React متكامل — تطبيق متجر إلكتروني 2026
📅 2026-10-19⏱ 13 دقائق قراءة
🎉 **مبروك! وصلت إلى المقال الأخير في مسار React!**
لقد تعلمت كل الأساسيات: JSX، المكونات، State، الأحداث، useEffect، القوائم، Router، و Context API. الآن حان الوقت لتطبيق كل هذه المفاهيم في **مشروع حقيقي متكامل**.
في هذا الدليل العملي، سنبني **متجراً إلكترونياً كاملاً** بـ React و TypeScript.
## ما سنبنيه
متجر إلكتروني بوظائف:
- **عرض المنتجات** في شبكة أنيقة.
- **البحث** في المنتجات.
- **التصفية** حسب الفئة والسعر.
- **سلة التسوق** مع إضافة/حذف.
- **صفحة تفاصيل المنتج**.
- **حفظ السلة** في localStorage.
- **عدد المنتجات** في السلة (Header).
- **إجمالي السعر** مع الضريبة.
## هيكل المشروع
```
react-shop/
├── src/
│ ├── components/
│ │ ├── Header.tsx
│ │ ├── ProductCard.tsx
│ │ ├── ProductGrid.tsx
│ │ ├── SearchBar.tsx
│ │ ├── Filters.tsx
│ │ └── CartSidebar.tsx
│ ├── contexts/
│ │ └── CartContext.tsx
│ ├── pages/
│ │ ├── Home.tsx
│ │ ├── ProductDetail.tsx
│ │ └── NotFound.tsx
│ ├── data/
│ │ └── products.ts
│ ├── types/
│ │ └── index.ts
│ ├── App.tsx
│ ├── main.tsx
│ └── index.css
```
## الخطوة 1: إنشاء المشروع
```bash
npm create vite@latest react-shop -- --template react-ts
cd react-shop
npm install
npm install react-router-dom
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
## الخطوة 2: إعداد Tailwind CSS
**`tailwind.config.js`:**
```javascript
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: { extend: {} },
plugins: [],
};
```
**`src/index.css`:**
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
font-family: 'Cairo', sans-serif;
direction: rtl;
background: #f9fafb;
}
```
## الخطوة 3: تعريف الأنواع
**`src/types/index.ts`:**
```typescript
export interface Product {
id: number;
name: string;
description: string;
price: number;
image: string;
category: string;
rating: number;
inStock: boolean;
}
export interface CartItem extends Product {
quantity: number;
}
export type Category = "الكل" | "إلكترونيات" | "ملابس" | "كتب" | "منزل";
```
## الخطوة 4: بيانات المنتجات
**`src/data/products.ts`:**
```typescript
import { Product } from "../types";
export const products: Product[] = [
{
id: 1,
name: "لابتوب Dell XPS 15",
description: "لابتوب احترافي بشاشة 15 بوصة ومعالج i7",
price: 5500,
image: "https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=400",
category: "إلكترونيات",
rating: 4.8,
inStock: true,
},
{
id: 2,
name: "iPhone 15 Pro",
description: "أحدث هاتف من Apple بكاميرا احترافية",
price: 4200,
image: "https://images.unsplash.com/photo-1592750475338-74b7b21085ab?w=400",
category: "إلكترونيات",
rating: 4.9,
inStock: true,
},
{
id: 3,
name: "قميص قطني",
description: "قميص رجالي قطني 100%",
price: 120,
image: "https://images.unsplash.com/photo-1596755094514-f87e34085b2c?w=400",
category: "ملابس",
rating: 4.5,
inStock: true,
},
{
id: 4,
name: "كتاب تعلم React",
description: "دليل شامل لتعلم React من الصفر",
price: 85,
image: "https://images.unsplash.com/photo-1544947950-fa07a98d237f?w=400",
category: "كتب",
rating: 4.7,
inStock: true,
},
{
id: 5,
name: "مصباح طاولة",
description: "مصباح حديث بتصميم أنيق",
price: 250,
image: "https://images.unsplash.com/photo-1507473885765-e6ed057f782c?w=400",
category: "منزل",
rating: 4.3,
inStock: false,
},
{
id: 6,
name: "سماعات Sony WH-1000XM5",
description: "سماعات لاسلكية مع إلغاء الضوضاء",
price: 1800,
image: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400",
category: "إلكترونيات",
rating: 4.9,
inStock: true,
},
{
id: 7,
name: "حقيبة جلد",
description: "حقيبة ظهر جلدية عملية",
price: 450,
image: "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=400",
category: "ملابس",
rating: 4.6,
inStock: true,
},
{
id: 8,
name: "ساعة ذكية",
description: "ساعة ذكية بميزات صحية متقدمة",
price: 1200,
image: "https://images.unsplash.com/photo-1546868871-7041f2a55e12?w=400",
category: "إلكترونيات",
rating: 4.4,
inStock: true,
},
];
```
## الخطوة 5: Cart Context
**`src/contexts/CartContext.tsx`:**
```tsx
import { createContext, useContext, useEffect, useState, ReactNode } from "react";
import { CartItem, Product } from "../types";
interface CartContextType {
items: CartItem[];
addItem: (product: Product) => void;
removeItem: (id: number) => void;
updateQuantity: (id: number, quantity: number) => void;
clearCart: () => void;
total: number;
itemCount: number;
}
const CartContext = createContext<CartContextType | undefined>(undefined);
export function CartProvider({ children }: { children: ReactNode }) {
const [items, setItems] = useState<CartItem[]>(() => {
try {
const stored = localStorage.getItem("cart");
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
});
useEffect(() => {
localStorage.setItem("cart", JSON.stringify(items));
}, [items]);
const addItem = (product: Product) => {
setItems((prev) => {
const existing = prev.find((item) => item.id === product.id);
if (existing) {
return prev.map((item) =>
item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item
);
}
return [...prev, { ...product, quantity: 1 }];
});
};
const removeItem = (id: number) => {
setItems((prev) => prev.filter((item) => item.id !== id));
};
const updateQuantity = (id: number, quantity: number) => {
if (quantity <= 0) {
removeItem(id);
return;
}
setItems((prev) =>
prev.map((item) => (item.id === id ? { ...item, quantity } : item))
);
};
const clearCart = () => setItems([]);
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);
return (
<CartContext.Provider
value={{ items, addItem, removeItem, updateQuantity, clearCart, total, itemCount }}
>
{children}
</CartContext.Provider>
);
}
export function useCart() {
const context = useContext(CartContext);
if (!context) throw new Error("useCart must be used within CartProvider");
return context;
}
```
## الخطوة 6: Header مع السلة
**`src/components/Header.tsx`:**
```tsx
import { Link } from "react-router-dom";
import { useCart } from "../contexts/CartContext";
interface HeaderProps {
onToggleCart: () => void;
}
export function Header({ onToggleCart }: HeaderProps) {
const { itemCount } = useCart();
return (
<header className="bg-white shadow-sm sticky top-0 z-40">
<div className="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
<Link to="/" className="text-2xl font-bold text-gray-900">
🛒 متجري
</Link>
<button
onClick={onToggleCart}
className="relative bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition"
>
السلة 🛍️
{itemCount > 0 && (
<span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs w-6 h-6 rounded-full flex items-center justify-center font-bold">
{itemCount}
</span>
)}
</button>
</div>
</header>
);
}
```
## الخطوة 7: ProductCard
**`src/components/ProductCard.tsx`:**
```tsx
import { Link } from "react-router-dom";
import { Product } from "../types";
import { useCart } from "../contexts/CartContext";
export function ProductCard({ product }: { product: Product }) {
const { addItem } = useCart();
return (
<div className="bg-white rounded-2xl overflow-hidden shadow-sm hover:shadow-lg transition border border-gray-100 group">
<Link to={`/product/${product.id}`}>
<div className="relative h-48 overflow-hidden">
<img
src={product.image}
alt={product.name}
className="w-full h-full object-cover group-hover:scale-105 transition duration-300"
/>
{!product.inStock && (
<div className="absolute inset-0 bg-black/60 flex items-center justify-center">
<span className="text-white font-bold">غير متوفر</span>
</div>
)}
</div>
</Link>
<div className="p-4">
<span className="inline-block bg-blue-100 text-blue-700 text-xs px-2 py-1 rounded-full mb-2">
{product.category}
</span>
<Link to={`/product/${product.id}`}>
<h3 className="font-semibold text-gray-900 mb-1 hover:text-blue-600 transition">
{product.name}
</h3>
</Link>
<div className="flex items-center gap-1 mb-3 text-sm text-yellow-500">
⭐ {product.rating}
</div>
<div className="flex items-center justify-between">
<span className="text-lg font-bold text-blue-600">{product.price} $</span>
<button
onClick={() => addItem(product)}
disabled={!product.inStock}
className="bg-blue-600 text-white px-3 py-1 rounded-lg text-sm hover:bg-blue-700 transition disabled:opacity-50 disabled:cursor-not-allowed"
>
أضف 🛒
</button>
</div>
</div>
</div>
);
}
```
## الخطوة 8: البحث والتصفية
**`src/components/SearchBar.tsx`:**
```tsx
interface SearchBarProps {
value: string;
onChange: (value: string) => void;
}
export function SearchBar({ value, onChange }: SearchBarProps) {
return (
<div className="relative">
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="ابحث عن منتج..."
className="w-full px-4 py-3 pr-10 rounded-xl border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">
🔍
</span>
</div>
);
}
```
**`src/components/Filters.tsx`:**
```tsx
import { Category } from "../types";
interface FiltersProps {
category: Category;
onCategoryChange: (cat: Category) => void;
sortBy: "default" | "price-asc" | "price-desc";
onSortChange: (sort: "default" | "price-asc" | "price-desc") => void;
}
const categories: Category[] = ["الكل", "إلكترونيات", "ملابس", "كتب", "منزل"];
export function Filters({ category, onCategoryChange, sortBy, onSortChange }: FiltersProps) {
return (
<div className="flex flex-wrap gap-4 mb-6">
<div className="flex flex-wrap gap-2">
{categories.map((cat) => (
<button
key={cat}
onClick={() => onCategoryChange(cat)}
className={`px-4 py-2 rounded-lg transition ${
category === cat
? "bg-blue-600 text-white"
: "bg-white text-gray-700 border border-gray-200 hover:border-blue-300"
}`}
>
{cat}
</button>
))}
</div>
<select
value={sortBy}
onChange={(e) => onSortChange(e.target.value as any)}
className="px-4 py-2 rounded-lg border border-gray-200"
>
<option value="default">ترتيب افتراضي</option>
<option value="price-asc">السعر: من الأقل</option>
<option value="price-desc">السعر: من الأعلى</option>
</select>
</div>
);
}
```
## الخطوة 9: الصفحة الرئيسية
**`src/pages/Home.tsx`:**
```tsx
import { useMemo, useState } from "react";
import { ProductCard } from "../components/ProductCard";
import { SearchBar } from "../components/SearchBar";
import { Filters } from "../components/Filters";
import { products } from "../data/products";
import { Category } from "../types";
export function Home() {
const [search, setSearch] = useState("");
const [category, setCategory] = useState<Category>("الكل");
const [sortBy, setSortBy] = useState<"default" | "price-asc" | "price-desc">("default");
const filtered = useMemo(() => {
let result = [...products];
// البحث
if (search) {
result = result.filter((p) =>
p.name.toLowerCase().includes(search.toLowerCase())
);
}
// التصفية
if (category !== "الكل") {
result = result.filter((p) => p.category === category);
}
// الترتيب
if (sortBy === "price-asc") {
result.sort((a, b) => a.price - b.price);
} else if (sortBy === "price-desc") {
result.sort((a, b) => b.price - a.price);
}
return result;
}, [search, category, sortBy]);
return (
<main className="max-w-7xl mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-gray-900 mb-6">
🛍️ تصفح المنتجات
</h1>
<div className="mb-6">
<SearchBar value={search} onChange={setSearch} />
</div>
<Filters
category={category}
onCategoryChange={setCategory}
sortBy={sortBy}
onSortChange={setSortBy}
/>
{filtered.length === 0 ? (
<div className="text-center py-16">
<span className="text-6xl mb-4 inline-block">🔍</span>
<p className="text-gray-500">لا توجد منتجات مطابقة</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{filtered.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
)}
</main>
);
}
```
## الخطوة 10: صفحة تفاصيل المنتج
**`src/pages/ProductDetail.tsx`:**
```tsx
import { useParams, Link } from "react-router-dom";
import { products } from "../data/products";
import { useCart } from "../contexts/CartContext";
export function ProductDetail() {
const { id } = useParams<{ id: string }>();
const product = products.find((p) => p.id === Number(id));
const { addItem } = useCart();
if (!product) {
return (
<div className="max-w-4xl mx-auto px-4 py-16 text-center">
<h1 className="text-3xl font-bold text-gray-900 mb-4">المنتج غير موجود</h1>
<Link to="/" className="text-blue-600 hover:underline">
العودة للمنتجات
</Link>
</div>
);
}
return (
<main className="max-w-6xl mx-auto px-4 py-8">
<Link to="/" className="text-blue-600 hover:underline mb-6 inline-block">
← العودة للمنتجات
</Link>
<div className="grid md:grid-cols-2 gap-8 bg-white rounded-2xl p-8 shadow-sm">
<div>
<img
src={product.image}
alt={product.name}
className="w-full h-96 object-cover rounded-xl"
/>
</div>
<div>
<span className="inline-block bg-blue-100 text-blue-700 text-sm px-3 py-1 rounded-full mb-4">
{product.category}
</span>
<h1 className="text-3xl font-bold text-gray-900 mb-4">
{product.name}
</h1>
<div className="flex items-center gap-2 mb-4 text-yellow-500">
⭐ {product.rating} / 5
</div>
<p className="text-gray-600 leading-relaxed mb-6">
{product.description}
</p>
<p className="text-4xl font-bold text-blue-600 mb-6">
{product.price} $
</p>
<button
onClick={() => addItem(product)}
disabled={!product.inStock}
className="w-full bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700 transition disabled:opacity-50"
>
{product.inStock ? "🛒 أضف للسلة" : "غير متوفر"}
</button>
</div>
</div>
</main>
);
}
```
## الخطوة 11: شريط جانبي للسلة
**`src/components/CartSidebar.tsx`:**
```tsx
import { useCart } from "../contexts/CartContext";
interface CartSidebarProps {
isOpen: boolean;
onClose: () => void;
}
export function CartSidebar({ isOpen, onClose }: CartSidebarProps) {
const { items, updateQuantity, removeItem, total, clearCart } = useCart();
const TAX_RATE = 0.15;
const tax = total * TAX_RATE;
const finalTotal = total + tax;
if (!isOpen) return null;
return (
<>
{/* Overlay */}
<div
className="fixed inset-0 bg-black/60 z-50"
onClick={onClose}
/>
{/* Sidebar */}
<div className="fixed top-0 left-0 h-full w-full max-w-md bg-white z-50 shadow-2xl flex flex-col">
<div className="p-4 border-b flex items-center justify-between">
<h2 className="text-xl font-bold">🛍️ سلة التسوق</h2>
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 text-2xl"
>
✕
</button>
</div>
<div className="flex-1 overflow-y-auto p-4">
{items.length === 0 ? (
<div className="text-center py-16">
<span className="text-6xl mb-4 inline-block">🛒</span>
<p className="text-gray-500">السلة فارغة</p>
</div>
) : (
<ul className="space-y-4">
{items.map((item) => (
<li key={item.id} className="flex gap-4 bg-gray-50 p-3 rounded-xl">
<img
src={item.image}
alt={item.name}
className="w-20 h-20 object-cover rounded-lg"
/>
<div className="flex-1">
<h3 className="font-semibold text-sm mb-1">{item.name}</h3>
<p className="text-blue-600 font-bold text-sm">
{item.price} $
</p>
<div className="flex items-center gap-2 mt-2">
<button
onClick={() => updateQuantity(item.id, item.quantity - 1)}
className="w-7 h-7 bg-gray-200 rounded-full"
>
-
</button>
<span className="font-bold">{item.quantity}</span>
<button
onClick={() => updateQuantity(item.id, item.quantity + 1)}
className="w-7 h-7 bg-gray-200 rounded-full"
>
+
</button>
<button
onClick={() => removeItem(item.id)}
className="text-red-500 mr-auto"
>
🗑️
</button>
</div>
</div>
</li>
))}
</ul>
)}
</div>
{items.length > 0 && (
<div className="p-4 border-t">
<div className="space-y-2 mb-4 text-sm">
<div className="flex justify-between">
<span>المجموع:</span>
<span>{total.toFixed(2)} $</span>
</div>
<div className="flex justify-between text-gray-500">
<span>الضريبة (15%):</span>
<span>{tax.toFixed(2)} $</span>
</div>
<div className="flex justify-between text-lg font-bold border-t pt-2">
<span>الإجمالي:</span>
<span className="text-blue-600">{finalTotal.toFixed(2)} $</span>
</div>
</div>
<button
onClick={() => {
alert("تم الطلب بنجاح!");
clearCart();
onClose();
}}
className="w-full bg-blue-600 text-white py-3 rounded-xl font-bold hover:bg-blue-700 transition mb-2"
>
✅ إتمام الطلب
</button>
<button
onClick={clearCart}
className="w-full text-red-600 py-2 text-sm hover:bg-red-50 rounded-lg transition"
>
إفراغ السلة
</button>
</div>
)}
</div>
</>
);
}
```
## الخطوة 12: App.tsx
**`src/App.tsx`:**
```tsx
import { useState } from "react";
import { Routes, Route } from "react-router-dom";
import { Header } from "./components/Header";
import { CartSidebar } from "./components/CartSidebar";
import { Home } from "./pages/Home";
import { ProductDetail } from "./pages/ProductDetail";
function App() {
const [isCartOpen, setIsCartOpen] = useState(false);
return (
<div className="min-h-screen">
<Header onToggleCart={() => setIsCartOpen(true)} />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/product/:id" element={<ProductDetail />} />
</Routes>
<CartSidebar isOpen={isCartOpen} onClose={() => setIsCartOpen(false)} />
<footer className="bg-gray-900 text-white text-center py-6 mt-16">
<p>© 2026 متجري — مشروع React متكامل</p>
</footer>
</div>
);
}
export default App;
```
## الخطوة 13: main.tsx
**`src/main.tsx`:**
```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { CartProvider } from "./contexts/CartContext";
import App from "./App";
import "./index.css";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>
<CartProvider>
<App />
</CartProvider>
</BrowserRouter>
</StrictMode>
);
```
## 🎉 تشغيل المشروع
```bash
npm run dev
```
افتح: `http://localhost:5173`
**🎉 مبروك! لديك متجر إلكتروني كامل!**
## ما تعلمته في هذا المشروع
في هذا المشروع، طبقت:
- ✅ **React Router** — التنقل بين الصفحات.
- ✅ **Context API** — إدارة السلة عالمياً.
- ✅ **useState** — للحالة المحلية.
- ✅ **useEffect** — لحفظ السلة في localStorage.
- ✅ **useMemo** — لتحسين أداء التصفية.
- ✅ **TypeScript** — للأنواع الآمنة.
- ✅ **Tailwind CSS** — لتصميم جميل.
- ✅ **Props** — لتمرير البيانات.
- ✅ **القوائم** — لعرض المنتجات.
- ✅ **الشروط** — لعرض "غير متوفر".
## تمارين إضافية
### تمرين 1: إضافة صفحة "من نحن"
أضف صفحة `/about` بتصميم بسيط.
### تمرين 2: تصفية حسب السعر
أضف نطاق سعر (min - max) للتصفية.
### تمرين 3: تقييم المنتج
أضف إمكانية تقييم المنتجات (نجوم).
### تمرين 4: المفضلة
أضف زر "المفضلة" لكل منتج.
### تمرين 5: Dark Mode
أضف وضعاً داكناً مع Context.
## حل المشاكل الشائعة
### 🔴 المشكلة 1: `useCart must be used within CartProvider`
**الحل:** تأكد من تغليف App بـ `<CartProvider>`.
### 🔴 المشكلة 2: الصفحة تُحدّث عند التنقل
**الحل:** تأكد من استخدام `<Link>` وليس `<a>`.
### 🔴 المشكلة 3: 404 في الإنتاج
**الحل:** أضف `rewrites` في `firebase.json`:
```json
"rewrites": [{ "source": "**", "destination": "/index.html" }]
```
### 🔴 المشكلة 4: السلة لا تُحفظ
**السبب:** localStorage لا يعمل في SSR.
**الحل:** استخدم `useState` مع lazy initialization.
## 🎉 مبروك! أكملت مسار React!
لقد وصلت إلى نهاية المسار. أنت الآن تعرف:
- ✅ **أساسيات React:** JSX، المكونات، Props.
- ✅ **إدارة الحالة:** useState، useEffect.
- ✅ **الأحداث والنماذج.**
- ✅ **القوائم والشروط.**
- ✅ **React Router.**
- ✅ **Context API.**
- ✅ **مشروع متكامل.**
## ماذا بعد React؟
الآن أنت جاهز لـ:
1. **Next.js** — إطار عمل كامل (SSR, SSG).
2. **Redux Toolkit** — إدارة حالة متقدمة.
3. **React Query** — جلب البيانات.
4. **TypeScript متقدم.**
5. **بناء معرض أعمالك** — ارفع مشاريعك على GitHub.
## الخلاصة
في هذا المشروع، بنيت:
- ✅ **متجر إلكتروني كامل.**
- ✅ **بحث وتصفية.**
- ✅ **سلة تسوق مع حفظ تلقائي.**
- ✅ **صفحة تفاصيل المنتج.**
- ✅ **حساب الضريبة والإجمالي.**
**هذا المشروع هو أساس كل تطبيقات التجارة الإلكترونية.** احتفظ بالكود، وطور فيه بنفسك!
**🎉 هل أكملت مسار React بالكامل؟ شاركنا في التعليقات!**