Effect থেকে Event আলাদা করা
Event handler শুধুমাত্র তখনই পুনরায় চালু হয় যখন আপনি একই interaction আবার সম্পাদন করেন। Event handler এর বিপরীতে, Effect পুনরায় সিঙ্ক্রোনাইজ হয় যদি তারা যে কোনো মান পড়ে, যেমন একটি prop বা একটি state variable, শেষ render এর সময় যা ছিল তার থেকে ভিন্ন হয়। কখনও কখনও, আপনি উভয় আচরণের মিশ্রণও চান: একটি Effect যা কিছু মানের প্রতিক্রিয়ায় পুনরায় চালু হয় কিন্তু অন্যদের জন্য নয়। এই পৃষ্ঠাটি আপনাকে শেখাবে কিভাবে এটি করতে হয়।
যা যা আপনি শিখবেন
- কিভাবে একটি event handler এবং একটি Effect এর মধ্যে বেছে নিতে হয়
- কেন Effect reactive, এবং event handler reactive নয়
- আপনার Effect এর কোডের একটি অংশ reactive না হতে চাইলে কি করতে হবে
- Effect Event কি, এবং কিভাবে আপনার Effect থেকে সেগুলি extract করতে হয়
- কিভাবে Effect Event ব্যবহার করে Effect থেকে সর্বশেষ props এবং state পড়তে হয়
Event handler এবং Effect এর মধ্যে বেছে নেওয়া
প্রথমে, আসুন event handler এবং Effect এর মধ্যে পার্থক্য পুনরায় দেখি।
কল্পনা করুন আপনি একটি chat room কম্পোনেন্ট implement করছেন। আপনার requirements এরকম দেখাচ্ছে:
- আপনার কম্পোনেন্ট স্বয়ংক্রিয়ভাবে নির্বাচিত chat room এ সংযুক্ত হওয়া উচিত।
- যখন আপনি “Send” button এ ক্লিক করেন, তখন এটি chat এ একটি message পাঠানো উচিত।
ধরা যাক আপনি ইতিমধ্যে তাদের জন্য কোড implement করেছেন, কিন্তু আপনি নিশ্চিত নন কোথায় এটি রাখবেন। আপনার কি event handler ব্যবহার করা উচিত নাকি Effect? প্রতিবার যখন আপনাকে এই প্রশ্নের উত্তর দিতে হবে, বিবেচনা করুন কেন কোডটি চালানো প্রয়োজন।
Event handler নির্দিষ্ট interaction এর প্রতিক্রিয়ায় চালু হয়
ব্যবহারকারীর দৃষ্টিকোণ থেকে, একটি message পাঠানো উচিত কারণ নির্দিষ্ট “Send” button ক্লিক করা হয়েছিল। ব্যবহারকারী বেশ বিরক্ত হবেন যদি আপনি তাদের message অন্য কোনো সময়ে বা অন্য কোনো কারণে পাঠান। এই কারণেই একটি message পাঠানো একটি event handler হওয়া উচিত। Event handler আপনাকে নির্দিষ্ট interaction handle করতে দেয়:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// ...
function handleSendClick() {
sendMessage(message);
}
// ...
return (
<>
<input value={message} onChange={e => setMessage(e.target.value)} />
<button onClick={handleSendClick}>Send</button>
</>
);
}একটি event handler এর সাথে, আপনি নিশ্চিত হতে পারেন যে sendMessage(message) শুধুমাত্র তখনই চালু হবে যদি ব্যবহারকারী button টি চাপেন।
Effect যখনই synchronization প্রয়োজন তখন চালু হয়
মনে করুন আপনাকে কম্পোনেন্টটিকে chat room এর সাথে সংযুক্ত রাখতে হবে। সেই কোডটি কোথায় যায়?
এই কোডটি চালানোর কারণ কোনো নির্দিষ্ট interaction নয়। ব্যবহারকারী কেন বা কিভাবে chat room screen এ navigate করেছে তা কোনো ব্যাপার না। এখন যেহেতু তারা এটি দেখছে এবং এটির সাথে interact করতে পারে, কম্পোনেন্টটিকে নির্বাচিত chat server এর সাথে সংযুক্ত থাকতে হবে। এমনকি যদি chat room কম্পোনেন্ট আপনার app এর প্রাথমিক screen হয়, এবং ব্যবহারকারী কোনো interaction সম্পাদন না করে থাকে, তবুও আপনাকে এখনও সংযুক্ত হতে হবে। এই কারণেই এটি একটি Effect:
function ChatRoom({ roomId }) {
// ...
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}এই কোডের সাথে, আপনি নিশ্চিত হতে পারেন যে বর্তমানে নির্বাচিত chat server এর সাথে সর্বদা একটি সক্রিয় connection আছে, নির্বিশেষে ব্যবহারকারী দ্বারা সম্পাদিত নির্দিষ্ট interaction এর। ব্যবহারকারী শুধুমাত্র আপনার app খুলেছে, একটি ভিন্ন room নির্বাচন করেছে, বা অন্য screen এ navigate করেছে এবং ফিরে এসেছে, আপনার Effect নিশ্চিত করে যে কম্পোনেন্টটি বর্তমানে নির্বাচিত room এর সাথে সিঙ্ক্রোনাইজড থাকবে, এবং যখনই এটি প্রয়োজন তখন পুনরায় সংযুক্ত হবে।
import { useState, useEffect } from 'react'; import { createConnection, sendMessage } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); function handleSendClick() { sendMessage(message); } return ( <> <h1>Welcome to the {roomId} room!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> <button onClick={handleSendClick}>Send</button> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); const [show, setShow] = useState(false); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <button onClick={() => setShow(!show)}> {show ? 'Close chat' : 'Open chat'} </button> {show && <hr />} {show && <ChatRoom roomId={roomId} />} </> ); }
Reactive value এবং reactive logic
স্বজ্ঞাতভাবে, আপনি বলতে পারেন যে event handler সর্বদা “manually” trigger হয়, উদাহরণস্বরূপ একটি button ক্লিক করে। অন্যদিকে Effect, “automatic”: তারা চালু হয় এবং পুনরায় চালু হয় যতবার সিঙ্ক্রোনাইজড থাকার প্রয়োজন হয়।
এটি সম্পর্কে চিন্তা করার আরও সুনির্দিষ্ট উপায় আছে।
আপনার কম্পোনেন্টের body এর ভিতরে ঘোষিত Props, state, এবং variable গুলিকে reactive value বলা হয়। এই উদাহরণে, serverUrl একটি reactive value নয়, কিন্তু roomId এবং message হল। তারা rendering data flow এ অংশগ্রহণ করে:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// ...
}এই ধরনের reactive value একটি re-render এর কারণে পরিবর্তিত হতে পারে। উদাহরণস্বরূপ, ব্যবহারকারী message edit করতে পারে বা একটি dropdown এ একটি ভিন্ন roomId বেছে নিতে পারে। Event handler এবং Effect পরিবর্তনের প্রতি ভিন্নভাবে প্রতিক্রিয়া জানায়:
- Event handler এর ভিতরের logic reactive নয়। এটি আবার চালু হবে না যদি না ব্যবহারকারী একই interaction (যেমন একটি click) আবার সম্পাদন করে। Event handler reactive value পড়তে পারে তাদের পরিবর্তনের প্রতি “react” না করে।
- Effect এর ভিতরের logic reactive। যদি আপনার Effect একটি reactive value পড়ে, আপনাকে এটি একটি dependency হিসাবে নির্দিষ্ট করতে হবে। তারপর, যদি একটি re-render সেই মানটি পরিবর্তন করে, React নতুন মান দিয়ে আপনার Effect এর logic পুনরায় চালু করবে।
আসুন এই পার্থক্যটি চিত্রিত করতে আগের উদাহরণটি পুনরায় দেখি।
Event handler এর ভিতরের logic reactive নয়
কোডের এই লাইনটি দেখুন। এই logic কি reactive হওয়া উচিত নাকি নয়?
// ...
sendMessage(message);
// ...ব্যবহারকারীর দৃষ্টিকোণ থেকে, message এ একটি পরিবর্তন মানে এই নয় যে তারা একটি message পাঠাতে চায়। এর মানে শুধুমাত্র এই যে ব্যবহারকারী টাইপ করছে। অন্য কথায়, যে logic একটি message পাঠায় তা reactive হওয়া উচিত নয়। এটি শুধুমাত্র reactive value পরিবর্তিত হওয়ার কারণে আবার চালু হওয়া উচিত নয়। এই কারণেই এটি event handler এ থাকে:
function handleSendClick() {
sendMessage(message);
}Event handler reactive নয়, তাই sendMessage(message) শুধুমাত্র তখনই চালু হবে যখন ব্যবহারকারী Send button চাপবে।
Effect এর ভিতরের logic reactive
এখন আসুন এই লাইনগুলিতে ফিরে যাই:
// ...
const connection = createConnection(serverUrl, roomId);
connection.connect();
// ...ব্যবহারকারীর দৃষ্টিকোণ থেকে, roomId এ একটি পরিবর্তন মানে যে তারা একটি ভিন্ন room এ সংযুক্ত হতে চায়। অন্য কথায়, room এ সংযুক্ত হওয়ার logic reactive হওয়া উচিত। আপনি চান যে কোডের এই লাইনগুলি reactive value এর সাথে “keep up” করুক, এবং সেই মান ভিন্ন হলে আবার চালু হোক। এই কারণেই এটি একটি Effect এ থাকে:
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId]);Effect reactive, তাই createConnection(serverUrl, roomId) এবং connection.connect() roomId এর প্রতিটি স্বতন্ত্র মানের জন্য চালু হবে। আপনার Effect chat connection টিকে বর্তমানে নির্বাচিত room এর সাথে সিঙ্ক্রোনাইজড রাখে।
Effect থেকে non-reactive logic বের করা
যখন আপনি reactive logic এর সাথে non-reactive logic মিশ্রিত করতে চান তখন জিনিসগুলি আরও জটিল হয়ে যায়।
উদাহরণস্বরূপ, কল্পনা করুন যে আপনি ব্যবহারকারী chat এ সংযুক্ত হলে একটি notification দেখাতে চান। আপনি props থেকে বর্তমান theme (dark বা light) পড়েন যাতে আপনি সঠিক রঙে notification দেখাতে পারেন:
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
// ...তবে, theme একটি reactive value (এটি re-rendering এর ফলে পরিবর্তিত হতে পারে), এবং একটি Effect দ্বারা পড়া প্রতিটি reactive value অবশ্যই তার dependency হিসাবে ডিক্লেয়ার করতে হবে। এখন আপনাকে আপনার Effect এর dependency হিসাবে theme নির্দিষ্ট করতে হবে:
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]); // ✅ সমস্ত dependency ডিক্লেয়ার করা হয়েছে
// ...এই উদাহরণটি নিয়ে খেলুন এবং দেখুন আপনি কি এই user experience এর সমস্যাটি খুঁজে পেতে পারেন:
import { useState, useEffect } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { showNotification('Connected!', theme); }); connection.connect(); return () => connection.disconnect(); }, [roomId, theme]); return <h1>Welcome to the {roomId} room!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Use dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
যখন roomId পরিবর্তিত হয়, chat আপনার প্রত্যাশা অনুযায়ী পুনরায় সংযুক্ত হয়। কিন্তু যেহেতু theme ও একটি dependency, chat এছাড়াও প্রতিবার পুনরায় সংযুক্ত হয় যখন আপনি dark এবং light theme এর মধ্যে switch করেন। এটা ভালো নয়!
অন্য কথায়, আপনি চান না যে এই লাইনটি reactive হোক, যদিও এটি একটি Effect এর ভিতরে আছে (যা reactive):
// ...
showNotification('Connected!', theme);
// ...আপনার এই non-reactive logic কে এর চারপাশের reactive Effect থেকে আলাদা করার একটি উপায় প্রয়োজন।
একটি Effect Event ডিক্লেয়ার করা
useEffectEvent নামক একটি বিশেষ Hook ব্যবহার করুন আপনার Effect থেকে এই non-reactive logic বের করতে:
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
// ...এখানে, onConnected কে একটি Effect Event বলা হয়। এটি আপনার Effect logic এর একটি অংশ, কিন্তু এটি একটি event handler এর মতো অনেক বেশি আচরণ করে। এর ভিতরের logic reactive নয়, এবং এটি সর্বদা আপনার props এবং state এর সর্বশেষ মান “দেখে”।
এখন আপনি আপনার Effect এর ভিতর থেকে onConnected Effect Event কল করতে পারেন:
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ সমস্ত dependency ডিক্লেয়ার করা হয়েছে
// ...এটি সমস্যার সমাধান করে। লক্ষ্য করুন যে আপনাকে আপনার Effect এর dependency এর তালিকা থেকে theme সরাতে হয়েছিল, কারণ এটি আর Effect এ ব্যবহৃত হচ্ছে না। আপনাকে এটিতে onConnected যোগ করারও প্রয়োজন নেই, কারণ Effect Event reactive নয় এবং dependency থেকে বাদ দিতে হবে।
যাচাই করুন যে নতুন আচরণটি আপনার প্রত্যাশা অনুযায়ী কাজ করে:
import { useState, useEffect } from 'react'; import { experimental_useEffectEvent as useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { const onConnected = useEffectEvent(() => { showNotification('Connected!', theme); }); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { onConnected(); }); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Welcome to the {roomId} room!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Use dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
আপনি Effect Event কে event handler এর খুব অনুরূপ হিসাবে ভাবতে পারেন। প্রধান পার্থক্য হল যে event handler user interaction এর প্রতিক্রিয়ায় চালু হয়, যেখানে Effect Event আপনার দ্বারা Effect থেকে trigger করা হয়। Effect Event আপনাকে Effect এর reactivity এবং যে কোড reactive হওয়া উচিত নয় তার মধ্যে “chain ভাঙতে” দেয়।
Effect Event দিয়ে সর্বশেষ props এবং state পড়া
Effect Event আপনাকে অনেক pattern ঠিক করতে দেয় যেখানে আপনি dependency linter suppress করতে প্রলুব্ধ হতে পারেন।
উদাহরণস্বরূপ, ধরুন আপনার একটি Effect আছে page visit log করার জন্য:
function Page() {
useEffect(() => {
logVisit();
}, []);
// ...
}পরে, আপনি আপনার site এ একাধিক route যোগ করেন। এখন আপনার Page কম্পোনেন্ট বর্তমান path সহ একটি url prop পায়। আপনি আপনার logVisit call এর অংশ হিসাবে url pass করতে চান, কিন্তু dependency linter অভিযোগ করে:
function Page({ url }) {
useEffect(() => {
logVisit(url);
}, []); // 🔴 React Hook useEffect has a missing dependency: 'url'
// ...
}কোডটি কি করতে চায় তা নিয়ে চিন্তা করুন। আপনি বিভিন্ন URL এর জন্য একটি পৃথক visit log করতে চান কারণ প্রতিটি URL একটি ভিন্ন page প্রতিনিধিত্ব করে। অন্য কথায়, এই logVisit call url এর সাপেক্ষে reactive হওয়া উচিত। এই কারণে, এই ক্ষেত্রে, dependency linter অনুসরণ করা এবং url কে একটি dependency হিসাবে যোগ করা বোধগম্য:
function Page({ url }) {
useEffect(() => {
logVisit(url);
}, [url]); // ✅ সমস্ত dependency ডিক্লেয়ার করা হয়েছে
// ...
}এখন ধরুন আপনি প্রতিটি page visit এর সাথে shopping cart এ item এর সংখ্যা অন্তর্ভুক্ত করতে চান:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
useEffect(() => {
logVisit(url, numberOfItems);
}, [url]); // 🔴 React Hook useEffect has a missing dependency: 'numberOfItems'
// ...
}আপনি Effect এর ভিতরে numberOfItems ব্যবহার করেছেন, তাই linter আপনাকে এটি একটি dependency হিসাবে যোগ করতে বলে। তবে, আপনি চান না যে logVisit call numberOfItems এর সাপেক্ষে reactive হোক। যদি ব্যবহারকারী shopping cart এ কিছু রাখে, এবং numberOfItems পরিবর্তিত হয়, এর মানে এই নয় যে ব্যবহারকারী আবার page টি visit করেছে। অন্য কথায়, page visit করা হল, কিছু অর্থে, একটি “event”। এটি সময়ের একটি সুনির্দিষ্ট মুহূর্তে ঘটে।
কোডটি দুটি অংশে বিভক্ত করুন:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});
useEffect(() => {
onVisit(url);
}, [url]); // ✅ সমস্ত dependency ডিক্লেয়ার করা হয়েছে
// ...
}এখানে, onVisit একটি Effect Event। এর ভিতরের কোড reactive নয়। এই কারণেই আপনি numberOfItems (বা অন্য কোনো reactive value!) ব্যবহার করতে পারেন চিন্তা না করে যে এটি পরিবর্তনের সময় আশেপাশের কোড পুনরায় execute হবে।
অন্যদিকে, Effect নিজেই reactive থাকে। Effect এর ভিতরের কোড url prop ব্যবহার করে, তাই Effect প্রতিটি re-render এর পরে একটি ভিন্ন url দিয়ে পুনরায় চালু হবে। এটি, পরিবর্তে, onVisit Effect Event কল করবে।
ফলস্বরূপ, আপনি url এর প্রতিটি পরিবর্তনের জন্য logVisit কল করবেন, এবং সর্বদা সর্বশেষ numberOfItems পড়বেন। তবে, যদি numberOfItems নিজে থেকে পরিবর্তিত হয়, এটি কোনো কোড পুনরায় চালু করবে না।
গভীরভাবে জানুন
বিদ্যমান codebase গুলিতে, আপনি কখনও কখনও এইভাবে lint rule suppress করা দেখতে পারেন:
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
useEffect(() => {
logVisit(url, numberOfItems);
// 🔴 এইভাবে linter suppress করা এড়িয়ে চলুন:
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
// ...
}useEffectEvent React এর একটি stable অংশ হয়ে গেলে, আমরা সুপারিশ করি কখনও linter suppress করবেন না।
Rule suppress করার প্রথম অসুবিধা হল যে React আর আপনাকে সতর্ক করবে না যখন আপনার Effect কে একটি নতুন reactive dependency এর প্রতি “react” করতে হবে যা আপনি আপনার কোডে introduce করেছেন। আগের উদাহরণে, আপনি dependency তে url যোগ করেছিলেন কারণ React আপনাকে এটি করতে মনে করিয়ে দিয়েছিল। আপনি linter disable করলে সেই Effect এ ভবিষ্যতের কোনো edit এর জন্য আর এই ধরনের reminder পাবেন না। এটি bug এর দিকে নিয়ে যায়।
এখানে linter suppress করার কারণে সৃষ্ট একটি বিভ্রান্তিকর bug এর উদাহরণ। এই উদাহরণে, handleMove function টি বর্তমান canMove state variable মান পড়ার কথা যাতে dot cursor অনুসরণ করবে কিনা তা সিদ্ধান্ত নিতে পারে। তবে, handleMove এর ভিতরে canMove সর্বদা true।
আপনি কি দেখতে পাচ্ছেন কেন?
import { useState, useEffect } from 'react'; export default function App() { const [position, setPosition] = useState({ x: 0, y: 0 }); const [canMove, setCanMove] = useState(true); function handleMove(e) { if (canMove) { setPosition({ x: e.clientX, y: e.clientY }); } } useEffect(() => { window.addEventListener('pointermove', handleMove); return () => window.removeEventListener('pointermove', handleMove); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <> <label> <input type="checkbox" checked={canMove} onChange={e => setCanMove(e.target.checked)} /> The dot is allowed to move </label> <hr /> <div style={{ position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity: 0.6, transform: `translate(${position.x}px, ${position.y}px)`, pointerEvents: 'none', left: -20, top: -20, width: 40, height: 40, }} /> </> ); }
এই কোডের সমস্যা হল dependency linter suppress করা। যদি আপনি suppression সরান, আপনি দেখবেন যে এই Effect handleMove function এর উপর নির্ভর করা উচিত। এটি বোধগম্য: handleMove component body এর ভিতরে ডিক্লেয়ার করা হয়েছে, যা এটিকে একটি reactive value করে তোলে। প্রতিটি reactive value অবশ্যই একটি dependency হিসাবে নির্দিষ্ট করতে হবে, অথবা এটি সময়ের সাথে সাথে stale হতে পারে!
মূল কোডের লেখক React কে “মিথ্যা বলেছেন” যে Effect কোনো reactive value এর উপর নির্ভর করে না ([])। এই কারণেই React canMove পরিবর্তিত হওয়ার পরে (এবং এর সাথে handleMove) Effect পুনরায় সিঙ্ক্রোনাইজ করেনি। কারণ React Effect পুনরায় সিঙ্ক্রোনাইজ করেনি, listener হিসাবে attached handleMove হল initial render এর সময় তৈরি handleMove function। Initial render এর সময়, canMove ছিল true, যে কারণে initial render থেকে handleMove চিরকাল সেই মান দেখবে।
যদি আপনি কখনও linter suppress না করেন, আপনি কখনও stale value এর সমস্যা দেখবেন না।
useEffectEvent এর সাথে, linter কে “মিথ্যা বলার” প্রয়োজন নেই, এবং কোডটি আপনার প্রত্যাশা অনুযায়ী কাজ করে:
import { useState, useEffect } from 'react'; import { experimental_useEffectEvent as useEffectEvent } from 'react'; export default function App() { const [position, setPosition] = useState({ x: 0, y: 0 }); const [canMove, setCanMove] = useState(true); const onMove = useEffectEvent(e => { if (canMove) { setPosition({ x: e.clientX, y: e.clientY }); } }); useEffect(() => { window.addEventListener('pointermove', onMove); return () => window.removeEventListener('pointermove', onMove); }, []); return ( <> <label> <input type="checkbox" checked={canMove} onChange={e => setCanMove(e.target.checked)} /> The dot is allowed to move </label> <hr /> <div style={{ position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity: 0.6, transform: `translate(${position.x}px, ${position.y}px)`, pointerEvents: 'none', left: -20, top: -20, width: 40, height: 40, }} /> </> ); }
এর মানে এই নয় যে useEffectEvent সর্বদা সঠিক সমাধান। আপনার শুধুমাত্র সেই কোডের লাইনগুলিতে এটি প্রয়োগ করা উচিত যা আপনি reactive হতে চান না। উপরের sandbox এ, আপনি চাননি যে Effect এর কোড canMove এর সাপেক্ষে reactive হোক।
Effect Dependency সরানো পড়ুন linter suppress করার অন্যান্য সঠিক বিকল্পের জন্য।
Effect Event এর সীমাবদ্ধতা
Effect Event আপনি কিভাবে ব্যবহার করতে পারেন তাতে খুবই সীমিত:
- শুধুমাত্র Effect এর ভিতর থেকে তাদের কল করুন।
- কখনও তাদের অন্য কম্পোনেন্ট বা Hook এ pass করবেন না।
উদাহরণস্বরূপ, এইভাবে একটি Effect Event ডিক্লেয়ার এবং pass করবেন না:
function Timer() {
const [count, setCount] = useState(0);
const onTick = useEffectEvent(() => {
setCount(count + 1);
});
useTimer(onTick, 1000); // 🔴 এড়িয়ে চলুন: Effect Event pass করা
return <h1>{count}</h1>
}
function useTimer(callback, delay) {
useEffect(() => {
const id = setInterval(() => {
callback();
}, delay);
return () => {
clearInterval(id);
};
}, [delay, callback]); // dependency তে "callback" নির্দিষ্ট করতে হবে
}পরিবর্তে, সর্বদা Effect Event সরাসরি সেই Effect এর পাশে ডিক্লেয়ার করুন যা তাদের ব্যবহার করে:
function Timer() {
const [count, setCount] = useState(0);
useTimer(() => {
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>
}
function useTimer(callback, delay) {
const onTick = useEffectEvent(() => {
callback();
});
useEffect(() => {
const id = setInterval(() => {
onTick(); // ✅ ভালো: শুধুমাত্র একটি Effect এর ভিতরে locally কল করা হয়
}, delay);
return () => {
clearInterval(id);
};
}, [delay]); // "onTick" (একটি Effect Event) কে dependency হিসাবে নির্দিষ্ট করার প্রয়োজন নেই
}Effect Event হল আপনার Effect কোডের non-reactive “pieces”। তাদের সেই Effect এর পাশে থাকা উচিত যা তাদের ব্যবহার করে।
পুনরালোচনা
- Event handler নির্দিষ্ট interaction এর প্রতিক্রিয়ায় চালু হয়।
- Effect যখনই synchronization প্রয়োজন তখন চালু হয়।
- Event handler এর ভিতরের logic reactive নয়।
- Effect এর ভিতরের logic reactive।
- আপনি Effect থেকে non-reactive logic Effect Event এ সরাতে পারেন।
- শুধুমাত্র Effect এর ভিতর থেকে Effect Event কল করুন।
- Effect Event অন্য কম্পোনেন্ট বা Hook এ pass করবেন না।
চ্যালেঞ্জ 1 / 4: একটি variable ঠিক করুন যা update হচ্ছে না
এই Timer কম্পোনেন্ট একটি count state variable রাখে যা প্রতি সেকেন্ডে বৃদ্ধি পায়। যে মান দ্বারা এটি বৃদ্ধি পাচ্ছে তা increment state variable এ সংরক্ষিত। আপনি plus এবং minus button দিয়ে increment variable নিয়ন্ত্রণ করতে পারেন।
তবে, আপনি যতবারই plus button ক্লিক করুন না কেন, counter এখনও প্রতি সেকেন্ডে এক করে বৃদ্ধি পাচ্ছে। এই কোডের সাথে কি সমস্যা? কেন Effect এর কোডের ভিতরে increment সর্বদা 1 এর সমান? ভুলটি খুঁজুন এবং ঠিক করুন।
import { useState, useEffect } from 'react'; export default function Timer() { const [count, setCount] = useState(0); const [increment, setIncrement] = useState(1); useEffect(() => { const id = setInterval(() => { setCount(c => c + increment); }, 1000); return () => { clearInterval(id); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <> <h1> Counter: {count} <button onClick={() => setCount(0)}>Reset</button> </h1> <hr /> <p> Every second, increment by: <button disabled={increment === 0} onClick={() => { setIncrement(i => i - 1); }}>–</button> <b>{increment}</b> <button onClick={() => { setIncrement(i => i + 1); }}>+</button> </p> </> ); }