আগের পর্বে আমরা useReducer শিখেছিলাম। এই পর্বে আমরা দেখব Context API — এমন একটা সমস্যার সমাধান যেটা React এর হাতেখড়ি সিরিজের পর্ব ১৪-তে (Lifting State Up) আংশিকভাবে ছুঁয়ে গিয়েছিলাম, কিন্তু পুরোপুরি সমাধান করা হয়নি।
Prop Drilling — সমস্যাটা কী?
Lifting State Up শিখেছিলাম — state কমন প্যারেন্টে রেখে props দিয়ে নিচে পাঠাতে হয়। কিন্তু component tree গভীর হলে, একটা ডেটা ৪-৫ লেভেল নিচে পাঠাতে প্রতিটা মাঝের component-কে সেটা "পাস থ্রু" করতে হয়, যদিও তাদের নিজেদের সেই ডেটার দরকার নেই। একে বলে Prop Drilling।
// ❌ Prop Drilling — theme শুধু সবার শেষে দরকার, কিন্তু সব লেভেলে পাস করতে হচ্ছে
function App() {
const [theme, setTheme] = React.useState("light");
return <Page theme={theme} />;
}
function Page({ theme }) {
return <Sidebar theme={theme} />; // Page-এর নিজের theme দরকার নেই, শুধু পাস করছে
}
function Sidebar({ theme }) {
return <UserMenu theme={theme} />; // Sidebar-এরও দরকার নেই
}
function UserMenu({ theme }) {
return <button style={{ background: theme === "dark" ? "#000" : "#fff" }}>মেনু</button>;
// এখানেই আসলে theme দরকার!
}
Page আর Sidebar-এর theme নিয়ে কোনো কাজ নেই, তবু বাধ্য হয়ে সেটা props হিসেবে বয়ে নিয়ে যাচ্ছে — এতে কোড পাল্টানো কঠিন, আর নতুন কোনো মাঝের component যোগ হলে সেটাতেও prop টাইপ করতে হয়।
Context API দিয়ে সমাধান
Context একটা "শর্টকাট" তৈরি করে — একবার একটা মান "প্রোভাইড" করলে, নিচের যেকোনো গভীরতার component সরাসরি সেটা "কনজিউম" করতে পারে, মাঝের কাউকে props বয়ে নিয়ে যেতে হয় না।
ধাপ ১ — Context তৈরি করা
// ThemeContext.js
import { createContext } from "react";
const ThemeContext = createContext(); // একটা খালি "চ্যানেল" তৈরি
export default ThemeContext;
ধাপ ২ — Provider দিয়ে মান সরবরাহ করা
<ThemeContext.Provider> দিয়ে একটা "এরিয়া" wrap করা হয় — এই wrapper-এর ভেতরের যেকোনো component সেই মান পড়তে পারবে।
import ThemeContext from "./ThemeContext.js";
function App() {
const [theme, setTheme] = React.useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Page />
</ThemeContext.Provider>
);
}
খেয়াল করো — Page-এ আর কোনো theme prop পাঠানো হচ্ছে না!
ধাপ ৩ — useContext দিয়ে মান পড়া
যেকোনো গভীরতার component useContext Hook দিয়ে সরাসরি সেই মান পড়তে পারে — মাঝের কোনো component-এ কিছু লাগে না।
import { useContext } from "react";
import ThemeContext from "./ThemeContext.js";
function Page() {
return <Sidebar />; // theme prop পাঠানোর দরকার নেই
}
function Sidebar() {
return <UserMenu />; // এখানেও দরকার নেই
}
function UserMenu() {
const { theme, setTheme } = useContext(ThemeContext); // সরাসরি পড়া!
return (
<button style={{ background: theme === "dark" ? "#000" : "#fff" }}>
মেনু
</button>
);
}
Page আর Sidebar সম্পূর্ণ পরিষ্কার — theme-এর ব্যাপারে কিছুই জানে না, শুধু UserMenu-ই সরাসরি Context থেকে টেনে নিচ্ছে।
লাইভ ডেমো — গভীর Component Tree-তে থিম শেয়ার করা
নিচে App → Page → Sidebar → UserMenu — চার লেভেল গভীর, কিন্তু কোনো prop pass করা হচ্ছে না।
Custom Hook দিয়ে Context আরও পরিষ্কার করা
React এর হাতেখড়ি পর্ব ১৭-এর Custom Hook প্যাটার্ন Context-এর সাথে দারুণ মেলে — প্রতিবার useContext(ThemeContext) না লিখে একটা সহজ Custom Hook বানানো যায়।
// ThemeContext.js
import { createContext, useContext } from "react";
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// এটাই Custom Hook — এখন useContext(ThemeContext) বারবার লিখতে হয় না
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme অবশ্যই ThemeProvider-এর ভেতরে ব্যবহার করতে হবে");
}
return context;
}
// main.jsx
import { ThemeProvider } from "./ThemeContext.js";
ReactDOM.createRoot(document.getElementById("root")).render(
<ThemeProvider>
<App />
</ThemeProvider>
);
// UserMenu.jsx
import { useTheme } from "./ThemeContext.js";
function UserMenu() {
const { theme, setTheme } = useTheme(); // অনেক পরিষ্কার!
// ...
}
এখানে Advanced JS পর্ব ১০-এর কাস্টম Error class-এর মতোই একটা throw new Error() যোগ করা হয়েছে — যদি কেউ ভুলবশত ThemeProvider-এর বাইরে useTheme ব্যবহার করে, তাহলে একটা স্পষ্ট এরর মেসেজ দেখাবে, গোলমেলে undefined এরর না।
একাধিক Context একসাথে
একটা প্রজেক্টে একাধিক ধরনের গ্লোবাল ডেটা (থিম, ইউজার, ভাষা) থাকলে, প্রতিটার জন্য আলাদা Context বানানো ভালো অভ্যাস — একটা "মেগা" Context-এ সব মিশিয়ে না ফেলা।
function App() {
return (
<ThemeProvider>
<UserProvider>
<LanguageProvider>
<MainApp />
</LanguageProvider>
</UserProvider>
</ThemeProvider>
);
}
এভাবে প্রতিটা Context নিজের দায়িত্বে সীমাবদ্ধ থাকে — যে component শুধু ইউজার ডেটা চায়, সে শুধু useUser() ব্যবহার করবে, থিম বা ভাষার সাথে জড়াবে না।
সতর্কতা — Context সব সমস্যার সমাধান না
Context খুব সুবিধাজনক, কিন্তু প্রতিটা state Context-এ রাখা ভুল অভ্যাস। যে state শুধু একটা component বা তার কাছের চাইল্ডদের দরকার, সেটা এখনো সাধারণ useState দিয়েই রাখা উচিত — Context শুধু "সত্যিকারের গ্লোবাল" ডেটার জন্য (থিম, লগইন করা ইউজার, ভাষা)।
আরেকটা সতর্কতা — পারফরম্যান্স: Context-এর মান পাল্টালে, সেই Context ব্যবহার করা সব component re-render হয়, তারা আসলে যা দরকার তার চেয়ে বেশি কিছু ব্যবহার করুক বা না করুক। বড় প্রজেক্টে এটা সমস্যা হতে পারে — সমাধান পর্ব ৩-৪-এ (useMemo, useCallback) দেখব।
একটা সম্পূর্ণ উদাহরণ — Auth Context
চলো একটা বাস্তব প্যাটার্ন দেখি — লগইন করা ইউজারের তথ্য পুরো অ্যাপ জুড়ে অ্যাক্সেসযোগ্য রাখা।
// AuthContext.js
const AuthContext = React.createContext();
export function AuthProvider({ children }) {
const [user, setUser] = React.useState(null);
function login(userData) {
setUser(userData);
}
function logout() {
setUser(null);
}
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return React.useContext(AuthContext);
}
// যেকোনো component-এ (গভীরতা নির্বিশেষে)
function ProfileButton() {
const { user, logout } = useAuth();
if (!user) return <p>লগইন করুন</p>;
return (
<div>
<span>স্বাগতম, {user.name}</span>
<button onClick={logout}>লগআউট</button>
</div>
);
}
এখন অ্যাপের যেকোনো জায়গায় (Navbar, Sidebar, Settings — যেখানেই দরকার) useAuth() কল করেই ইউজার তথ্য পাওয়া যাবে, কোনো prop pass ছাড়াই।
নিজে চেষ্টা করো
Exercise:
- উপরের
ThemeProvider/useThemeনিজে বানাও, চার লেভেল গভীর component tree-তে ব্যবহার করো। - উপরের
AuthContextনিজে বানাও, একটা লগইন ফর্ম আর একটা প্রোফাইল বাটন — দুটো ভিন্ন component-এ ব্যবহার করো। - একটা
LanguageContextবানাও, ২-৩টা component-এ বাংলা/ইংরেজি টেক্সট দেখাও Context থেকে। - ইচ্ছাকৃতভাবে
Provider-এর বাইরেuseTheme()কল করো, দেখো এরর মেসেজ কী দেখায়।
পরের পর্বে যা থাকছে
পরের পর্বে আমরা শিখব useMemo — ভারী গণনা কীভাবে ক্যাশ করা যায়, যাতে অপ্রয়োজনীয় re-render-এ বারবার একই ভারী কাজ না করতে হয়।