Advanced React সিরিজের সাত পর্বে আমরা শিখেছি useReducer, Context API, useMemo, useCallback, Custom Hook, React.memo, আর Error Boundary/Code Splitting। এই শেষ পর্বে সবকিছু একসাথে মিলিয়ে বানাব একটা Global State-সহ শপিং কার্ট ড্যাশবোর্ড — যেখানে কার্টের ডেটা পুরো অ্যাপ জুড়ে অ্যাক্সেসযোগ্য, আর পারফরম্যান্স-সচেতনভাবে লেখা।
প্রজেক্টে যা যা প্রয়োগ হচ্ছে
- useReducer (পর্ব ১) — কার্টের সব action (যোগ, বাদ, কোয়ান্টিটি) একটা reducer-এ
- Context API (পর্ব ২) — কার্ট state prop drilling ছাড়াই পুরো অ্যাপে অ্যাক্সেসযোগ্য
- useMemo (পর্ব ৩) — কার্টের টোটাল দাম শুধু দরকার হলেই আবার গণনা
- useCallback (পর্ব ৪) — dispatch-ভিত্তিক ফাংশনগুলো স্থিতিশীল রেফারেন্স রাখা
- Custom Hook (পর্ব ৫) —
useCart()দিয়ে সহজ অ্যাক্সেস - React.memo (পর্ব ৬) — প্রোডাক্ট কার্ড অপ্রয়োজনীয় re-render থেকে সুরক্ষিত
- Error Boundary (পর্ব ৭) — কার্ট প্যানেল ক্র্যাশ করলেও বাকি অ্যাপ ঠিক থাকবে
ধাপ ১ — Cart Reducer (পর্ব ১-এর প্যাটার্ন)
// cartReducer.js
export function cartReducer(state, action) {
switch (action.type) {
case "ADD_ITEM": {
const existing = state.find((i) => i.id === action.payload.id);
if (existing) {
return state.map((i) =>
i.id === action.payload.id ? { ...i, qty: i.qty + 1 } : i
);
}
return [...state, { ...action.payload, qty: 1 }];
}
case "REMOVE_ITEM":
return state.filter((i) => i.id !== action.payload.id);
case "UPDATE_QTY":
return state.map((i) =>
i.id === action.payload.id
? { ...i, qty: Math.max(1, action.payload.qty) }
: i
);
case "CLEAR_CART":
return [];
default:
return state;
}
}
ধাপ ২ — CartContext + useCart (পর্ব ২ ও ৫-এর মিশ্রণ)
Context আর Custom Hook একসাথে — যেকোনো component থেকে useCart() কল করলেই কার্ট, দাম, আর সব action পাওয়া যাবে।
// CartContext.js
import { createContext, useContext, useReducer, useMemo, useCallback } from "react";
import { cartReducer } from "./cartReducer.js";
const CartContext = createContext();
export function CartProvider({ children }) {
const [cart, dispatch] = useReducer(cartReducer, []);
// useMemo (পর্ব ৩) — cart না পাল্টালে totalPrice আবার গণনা হবে না
const totalPrice = useMemo(() => {
return cart.reduce((sum, item) => sum + item.price * item.qty, 0);
}, [cart]);
const totalItems = useMemo(() => {
return cart.reduce((sum, item) => sum + item.qty, 0);
}, [cart]);
// useCallback (পর্ব ৪) — প্রতিটা ফাংশনের রেফারেন্স স্থিতিশীল
const addItem = useCallback((product) => {
dispatch({ type: "ADD_ITEM", payload: product });
}, []);
const removeItem = useCallback((id) => {
dispatch({ type: "REMOVE_ITEM", payload: { id } });
}, []);
const updateQty = useCallback((id, qty) => {
dispatch({ type: "UPDATE_QTY", payload: { id, qty } });
}, []);
const clearCart = useCallback(() => {
dispatch({ type: "CLEAR_CART" });
}, []);
const value = { cart, totalPrice, totalItems, addItem, removeItem, updateQty, clearCart };
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}
export function useCart() {
const context = useContext(CartContext);
if (!context) {
throw new Error("useCart অবশ্যই CartProvider-এর ভেতরে ব্যবহার করতে হবে");
}
return context;
}
ধাপ ৩ — Memoized ProductCard (পর্ব ৬)
// ProductCard.jsx
import { memo } from "react";
import { useCart } from "./CartContext.js";
const ProductCard = memo(function ProductCard({ product }) {
const { addItem } = useCart(); // addItem স্থিতিশীল, তাই এই component অপ্রয়োজনে re-render হবে না
return (
<div className="product-card">
<h3>{product.name}</h3>
<p>৳{product.price}</p>
<button onClick={() => addItem(product)}>কার্টে যোগ করো</button>
</div>
);
});
export default ProductCard;
ধাপ ৪ — CartPanel, Error Boundary দিয়ে সুরক্ষিত (পর্ব ৭)
// ErrorBoundary.jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <p>কার্ট প্যানেলে সমস্যা হয়েছে। বাকি সাইট ঠিকই কাজ করছে।</p>;
}
return this.props.children;
}
}
// CartPanel.jsx
import { useCart } from "./CartContext.js";
function CartPanel() {
const { cart, totalPrice, totalItems, removeItem, updateQty, clearCart } = useCart();
if (cart.length === 0) {
return <p>কার্ট খালি</p>;
}
return (
<div>
<h3>কার্ট ({totalItems}টা আইটেম)</h3>
{cart.map((item) => (
<div key={item.id}>
{item.name} × {item.qty} = ৳{item.price * item.qty}
<button onClick={() => removeItem(item.id)}>মুছো</button>
</div>
))}
<p>মোট: ৳{totalPrice}</p>
<button onClick={clearCart}>কার্ট খালি করো</button>
</div>
);
}
ধাপ ৫ — সবকিছু একসাথে (App)
// App.jsx
import { CartProvider } from "./CartContext.js";
import ProductGrid from "./ProductGrid.jsx";
import CartPanel from "./CartPanel.jsx";
import ErrorBoundary from "./ErrorBoundary.jsx";
function App() {
return (
<CartProvider>
<div>
<ProductGrid />
<ErrorBoundary>
<CartPanel />
</ErrorBoundary>
</div>
</CartProvider>
);
}
ProductGrid-এর ভেতরের সব ProductCard কোনো prop drilling ছাড়াই useCart() দিয়ে সরাসরি কার্টে যোগ করতে পারছে, আর CartPanel Error Boundary দিয়ে সুরক্ষিত — এটা ভেঙে গেলেও প্রোডাক্ট গ্রিড ঠিকই দেখা যাবে।
লাইভ ডেমো — সম্পূর্ণ কার্ট সিস্টেম
নিচে সবকিছু একসাথে কাজ করছে — প্রোডাক্ট কার্ড থেকে যোগ করো, কার্ট প্যানেল থেকে কোয়ান্টিটি বদলাও বা মুছে ফেলো। প্রোডাক্ট কার্ডগুলো React.memo দিয়ে সুরক্ষিত, তাই কার্ট প্যানেল আপডেট হলেও কার্ডগুলো re-render হয় না।
প্রতিটা প্রোডাক্ট কার্ডে "render: N" সংখ্যাটা লক্ষ্য করো — কার্ট প্যানেলে কোয়ান্টিটি বদলালেও এই সংখ্যা বাড়ে না, কারণ React.memo আর useCallback মিলে অপ্রয়োজনীয় re-render আটকাচ্ছে।
কোড কীভাবে সিরিজের সব শেখা মেলাচ্ছে — সংক্ষেপে
| প্রজেক্টের অংশ | কোন পর্বের কনসেপ্ট |
|---|---|
cartReducer ও action/dispatch |
পর্ব ১ (useReducer) |
CartContext.Provider, prop drilling এড়ানো |
পর্ব ২ (Context API) |
totalPrice, totalItems useMemo |
পর্ব ৩ (useMemo) |
addItem, removeItem useCallback |
পর্ব ৪ (useCallback) |
useCart() Custom Hook |
পর্ব ৫ (Custom Hook) |
memo(ProductCard) |
পর্ব ৬ (React.memo) |
ErrorBoundary-এ CartPanel wrap করা |
পর্ব ৭ (Error Boundary) |
পরবর্তী উন্নতির আইডিয়া
Exercise: প্রজেক্টটা আরও ভালো করার জন্য নিজে চেষ্টা করো —
- React এর হাতেখড়ি পর্ব ৯-এর
useLocalStorageদিয়ে কার্ট রিফ্রেশের পরও ধরে রাখো (Custom Hook একসাথে মেলানো, পর্ব ৫-এর প্যাটার্ন)। React.lazyদিয়েCartPanellazy-load করো, শুধু ব্যবহারকারী কার্ট আইকনে ক্লিক করলেই লোড হবে।- React Router (পর্ব ১৫) দিয়ে একটা আলাদা checkout পেজ বানাও, যেখানেও
useCart()ব্যবহার হবে। - React DevTools Profiler (পর্ব ৬) দিয়ে যাচাই করো, সত্যিই
ProductCard-গুলো কার্ট আপডেটে re-render হচ্ছে না।
সিরিজ শেষ!
Advanced React সিরিজের আট পর্বে আমরা useReducer থেকে শুরু করে Context, useMemo/useCallback, Custom Hook, React.memo, Error Boundary, আর Code Splitting পর্যন্ত শিখেছি, আর আজ সেগুলো একসাথে মিলিয়ে একটা production-মানের প্যাটার্ন দিয়ে একটা কার্ট সিস্টেম বানিয়েছি। HTML থেকে শুরু করে React পর্যন্ত পুরো এই যাত্রায় শেখা সবকিছু এখন তোমার যেকোনো বাস্তব প্রজেক্টে প্রয়োগ করার জন্য প্রস্তুত।