React-এর অনেক বাগের জন্ম হয় useEffect-এ, hook-টা ভাঙা বলে নয়। কারণ মানুষ কোনো কিছু কোথায় রাখবে বুঝতে না পারলেই এটার দিকে হাত বাড়ায়। কোনো value বদলালে কিছু করতে হবে? useEffect। দুটো state sync রাখতে হবে? useEffect। এটা component-এর জাঙ্ক ড্রয়ারে পরিণত হয়।
সমস্যা হলো একটা effect render-এর পরে চলে, render-এরই একটা side effect হিসেবে। তাই আপনি যা দেখাতে যাচ্ছেন তা হিসাব করতে effect ব্যবহার করা মাত্রই একটা অতিরিক্ত render, stale data-র সুযোগ ও ভুল হতে পারে এমন একটা dependency array যোগ হয়ে যায়। review-তে আমি যেসব effect মুছি তার বেশিরভাগ তিনটা ভাগে পড়ে। সবচেয়ে খারাপটা আগে।
Render-এর জন্য ডেটা রূপান্তর করতে effect লাগে না
এটাই সবচেয়ে বড়টা। আপনার কিছু state আছে আর তা থেকে একটা derived value চান, তাই derived value-টা আরেকটা state-এ রাখেন আর সেটা হালনাগাদ রাখতে একটা effect ব্যবহার করেন।
function Profile({ first, last }) {
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(first + " " + last);
}, [first, last]);
return <h1>{fullName}</h1>;
}এটা আসলে কী করে দেখুন। component একটা খালি fullName নিয়ে render হয়। তারপর effect চলে, setFullName কল করে আর সঠিক value নিয়ে একটা দ্বিতীয় render ঘটায়। তাই প্রতিটা prop বদলের দাম দুইটা render, আর এক frame-এর জন্য fullName পুরনো থাকে। এত কিছু শুধু একটা string হিসাব করতে।
যা আপনার কাছে আগে থেকেই আছে তা থেকে হিসাব করা গেলে, render-এর সময়েই হিসাব করুন। কোনো state না, কোনো effect না:
function Profile({ first, last }) {
const fullName = first + " " + last;
return <h1>{fullName}</h1>;
}এটা সহজ, সবসময় সঠিক আর মাঝখানে কোনো render নেই যেখানে নাম ভুল থাকে। হিসাবটা যদি সত্যিই ব্যয়বহুল হয় — string জোড়া লাগানো নয়, বরং বড় একটা list-এর উপর ভারী কিছু — সেটাকে useMemo-তে মুড়ে দিন। এটাও render-এর সময়েই হিসাব করে; শুধু input না বদলালে কাজটা এড়িয়ে যায়। যা আপনার প্রায় কখনোই দরকার হয় না তা হলো derived data-কে state-এ সংরক্ষণ করা।
আপনার কাছে থাকা state থেকে একটা value হিসাব করা গেলে, render-এর সময়েই সেটা হিসাব করুন। আরেকটা state-এ কপি করে effect দিয়ে sync রাখা শুধু একটা অতিরিক্ত render আর stale হওয়ার সুযোগ কিনে দেয়।
Prop বদলালে state reset করতে effect লাগে না
এটা একটা ক্লাসিক উদাহরণ। একটা profile page-কে আলাদা ব্যবহারকারীর দিকে navigate করলেই তার edit form খালি করতে হয়।
function Editor({ userId }) {
const [draft, setDraft] = useState("");
useEffect(() => {
setDraft(""); // reset when the user changes
}, [userId]);
// ...
}এটা কাজ করে, কিন্তু কঠিন পথে — reset-on-change ঠিক সেই কাজ যার জন্য React-এর key বানানো। component-কে এমন একটা key দিন যা ব্যবহারকারীর সঙ্গে বদলায়, আর React পুরনো instance ফেলে দিয়ে নতুন একটা mount করে, তার সব state আবার শুরুর মানে ফিরে যায়। কোনো effect না, প্রতিটা field হাতে reset করার দরকার নেই:
<Editor key={userId} userId={userId} />key বদলানো React-এর নিজস্ব "আবার শুরু করো" বাটন। কোনো identity বদলানোর কারণে state reset করাই যখন একটা effect-এর পুরো কাজ হয়ে দাঁড়ায়, তখন প্রায় সবসময় একটা key সেটা আরও পরিষ্কারভাবে করে — আর এটা component-এর সব state reset করে, শুধু আপনার মনে থাকা একটা field নয়।
ব্যবহারকারী কিছু করলে সেটার জন্য effect লাগে না
ব্যবহারকারী click, type বা submit করার কারণে কোনো code চলা উচিত হলে সেটা event handler-এ থাকা উচিত — click যে state বদলেছে সেটা দেখা কোনো effect-এ নয়।
// Roundabout: click sets state, effect reacts to the state.
function Cart() {
const [items, setItems] = useState([]);
useEffect(() => {
if (items.length > 0) {
showToast("Added to cart");
}
}, [items]);
function addItem(item) {
setItems([...items, item]);
}
}এখন items যেকোনো কারণে বদলালেই toast চলে — যার মধ্যে "ব্যবহারকারী কিছু যোগ করেছে" নয় এমন কিছুও আছে, যেমন একটা সংরক্ষিত cart লোড হওয়া। logic-টা যা কারণ ঘটিয়েছিল তা থেকে বিচ্ছিন্ন হয়ে গেছে। যেখানে এটা ঘটেছিল সেখানেই ফিরিয়ে দিন:
function addItem(item) {
setItems([...items, item]);
showToast("Added to cart"); // it happened because of this click
}Effect হলো React-এর বাইরের জিনিসের সঙ্গে sync রাখার জন্য — network, DOM, একটা subscription, একটা timer। এগুলো আপনার নিজের state বদলের প্রতিক্রিয়া দেখানোর জন্য নয়। কোন নির্দিষ্ট user action-এ code চলা উচিত তা যদি বলতে পারেন, সেই action-এর handler-ই তার আসল জায়গা।
দ্রুত পরীক্ষা
effect লেখার আগে জিজ্ঞেস করুন: "এটা কোন event-এর প্রতিক্রিয়া?" উত্তর যদি একটা user action হয় (click, submit, keypress), সেটা handler-এ যাবে। শুধু উত্তর "React-এর বাইরে কিছু বদলেছে" হলেই — একটা response এসেছে, tab আবার focus পেয়েছে, একটা socket data পাঠিয়েছে — সেটা effect-এ থাকার কথা।
যেসব effect থাকা উচিত
এর মানে এই না যে effect খারাপ। এর মানে বেশিরভাগ ভুল জায়গায় রাখা। যেগুলো সত্যিই মানানসই সেগুলো হলো যেসব আপনার component-কে বাইরের জগতের সঙ্গে sync রাখে:
- কোনো কিছুতে subscribe করা আর cleanup-এ unsubscribe করা — একটা WebSocket, একটা event listener, React-এর বাইরের একটা store।
- component mount হলে বা কোনো id বদলালে fetch শুরু করা (আর React Query-র মতো library ঠিক এই কারণেই আছে, কারণ এটা ভালোভাবে করা — caching, cancellation, race handling — একটা effect-এর চেয়ে অনেক বেশি কাজ)।
- render-এর পর সরাসরি DOM ছোঁয়া — একটা input-এ focus করা, একটা element মাপা, non-React widget চালানো।
এসবের গঠন একই: React-কে এমন কিছুর সঙ্গে তাল মিলিয়ে চলতে হয় যা এর নিয়ন্ত্রণে নেই। এটাই useEffect-এর আসল কাজ।
যে cleanup কেউ লেখে না: fetch-এ race condition
fetch করাই সেই effect যা প্রায় সবাই রেখে দেয়, তাই এখানে প্রায় সবাই যে বাগ রেখে যায় তা দেখুন। id বদলালে fetch করেন, id দ্রুত দুইবার বদলায় আর response ফিরে আসে এলোমেলো ক্রমে — ধীর প্রথম request দ্রুত দ্বিতীয়টার পরে পৌঁছায় আর সেটাকে overwrite করে। এখন URL-এ সঠিক id থাকলেও ভুল ব্যবহারকারীর data দেখাচ্ছেন।
useEffect(() => {
let active = true;
fetchUser(id).then((user) => {
if (active) setUser(user); // ignore if a newer request started
});
return () => {
active = false; // this effect is stale now
};
}, [id]);cleanup function চলমান request-টাকে পরিত্যক্ত হিসেবে চিহ্নিত করে, তাই তার result উপেক্ষা করা হয়। effect cleanup ঠিক এই কাজের জন্য — আর এটা বাদ দিলেই সেই মাথা-খারাপ-করা বাগ পান যা শুধু কেউ দ্রুত click করলে দেখা যায়।
যে একটা অভ্যাস বেশিরভাগ ঠিক করে দেয়
useEffect লেখার আগে একটাই প্রশ্ন করুন: এটা কি React-এর বাইরের কিছুর সঙ্গে sync রাখছে? হ্যাঁ হলে এটা সম্ভবত একটা আসল effect — লিখুন, cleanup ভুলবেন না। না হলে — আপনি যদি একটা value হিসাব করছেন, click-এর প্রতিক্রিয়া দেখাচ্ছেন, বা state reset করছেন — effect ভুল টুল, আর এর চেয়ে সহজ ও বেশি সঠিক উত্তর আছে।
কম effect কোনো স্টাইলগত পছন্দ নয়। আপনি যেটা সরান সেটার সঙ্গে একটা অতিরিক্ত render চলে যায়, একটা stale-state জানালা বন্ধ হয় আর একটা dependency array-কে আর সৎ রাখতে হয় না। সবচেয়ে ভালো useEffect সাধারণত সেটাই যেটা আপনি বুঝেছিলেন দরকার নেই।