এই পার্টে আমরা সর্বপ্রথম useRef
হুক নিয়ে কথা বলবো। রিয়াক্টে আমরা আমদের সকল কিছু স্টেট দিয়ে ম্যানেজ করতে পারি, কিন্তু কখনো কখনো হয়তো আমাদের HTML DOM Element
কে মডিফাই করার প্রয়োজন পরতে পারে যা আমরা চাইলেও স্টেট দিয়ে ম্যানেজ করতে পারবোনা। সেসব ক্ষেত্রে আমাদেরকে বাধ্য হয়েও ম্যনুয়ায়লি DOM
মডিফাই করার প্রয়োজন পরবে, এই ক্ষেত্রে রিয়াক্ট আমাদের একটি হুক দিয়ে দিয়েছে useRef
নামে, যেটা আমরা ব্যাবহার করতে পারি। তবে মনে রাখতে হবে useRef
আমারা রিয়াক্টের কনট্রোলের বাইরে গিয়ে ব্যবাহার করবো বিধায় এটাকে ওভার ইউজ করা যাবেনা। আমরা কিভাবে useRef
ব্যাবহার করবো এবং কোন কোন ক্ষেত্রে আমাদের ইউজ-রেফ ব্যাবহার করতে হবে তা নিয়ে আমারা এই লেসনে বিস্তারিত আলোচনা করবোঃ
Referencing Values with Refs
যখন আমরা চাই যে আমাদের কোন কম্পোনেন্ট কোন ভ্যালু ধরে রাখবে, এবং আমরা তা চেঞ্জ করলেও আমাদের কম্পোনেন্ট রি-রেন্ডার করবেনা, সেই ক্ষেত্রে আমারা useRef
ব্যাবহার করবো।
Adding a ref to your component (কিভাবে কম্পোনেন্টে useRef
ব্যবাহার করা যায়)
চলুন দেখে নেই কিভাবে আমরা কমোনেন্টে ইউজ রেফ ব্যাবহার করতে পারি,
- প্রথমে আমাদের কে রিয়াক্ট থেকে
useRef
কে ইম্পোর্ট করে নিতে হবে
import { useRef } from "react";
- ** তারপর আমাদের কম্পোনেন্টে
useRef
কে কল করতে হবে এবং তার একটি ইনিশিয়াল ভ্যালু দিতে হবে**
const ref = useRef(0);
আমারা এখানে ref
এর ইনিশিয়াল ভ্যালু দিয়েছি 0 । এই অবস্থায় ref
আমাদের একটা অবজেক্ট রিটার্ন করবে এবং ref
অবজেক্ট এর একটা ডিফল্ট প্রপার্টি থাকে current
নামে। সেই current
এর ভিতর আমরা আমাদের ref
এর ভ্যালুটা পাবো । আমরা যদি এবার ref
টাকে js console.log(ref)
করি, তাহলে আমরা আঊটপুট পাবো এমনঃ
{
current: 0; // আমরা যেই ইনিশয়াল ভ্যালুটা দিয়েছিলাম সেটা এখানে রিটার্ন করছে।
}
আমরা এই ref.current
এর ভ্যালু চাইলে read / write
দুটোই করতে পারি, অর্থাৎ আমরা চাইলে ref.current
এর মধ্যে কোন ভ্যালু স্টোর করতে পারি, সেটাকে পরিবর্তন করতে পারি এবং সেটাকে ব্যাবহার ও করতে পারি এবং এটার কোন ট্র্যাক রিয়াক্ট রাখবেনা, তাই এটাকে বলা হয় Escape Hatch
. তবে একটা বিষয় মাথায় রাখতে হবে যে আমরা ref.current
এর ভ্যালু রিয়াক্ট এর রেন্ডার এ ব্যাবহার করতে পারবোনা। অর্থাৎ UI তে ref.current
এর ভ্যালু দেখাতে পারবোনা।
import { useRef } from "react";
export default function App() {
const ref = useRef("something");
return <h1>You are rendering {ref.current} </h1>; // এখানে রিয়াক্ট এর রেন্ডারিং এ ref.current এর ভ্যালু UI তে দেখানো হচ্ছে, এটা করা যাবেনা।
}
তাহলে কি করা যাবে ? চলুন দেখে নেই একটা উদাহরণের মাধ্যমে।
আমরা একটা সিম্পল বাটন রাখবো এবং আমরা কয়বার বাটনে ক্লিক করলাম সেটা একটা এলার্টের মাধ্যমে দেখাবো।
import { useRef } from "react";
export default function App() {
const ref = useRef(0);
function handleCountClick() {
ref.current = ref.current + 1;
alert(`you clicked the button ${ref.current} times`);
}
return <button onClick={handleCountClick}>Click me to count</button>;
}
এখানে আমরা যতবার বাটনে ক্লিক করবো ততবার আমদের এলার্টে সেটা দেখাবে।
আমরা যদি এই কম্পোনেন্টে একটু খেয়াল করি তাহলেই useRef
এর ব্যাপারে স্বচ্ছ ধারনা পেয়ে যাবো।
খেয়াল করুনঃ
useRef
এরref.cuurent
ভ্যালু পরিবর্তন করা হয়েছে একটা হ্যান্ডেলারের ভিতর।- useRef
এর
ref.cuurentভ্যালু পরিবর্তন করার জন্য কোন রকম
setter function` কল করা হয়নি setter function
কল করা হয়নি বলে কম্পোনেন্ট রি-রেন্ডার করবেনা- কম্পোনেন্ট রি-রেন্ডার না করা সত্ত্বেও
ref
এর ভ্যালু পরিবর্তন করার সাথে সাথেই পরের লাইনেই আমরা আপডেটেড ভ্যালু এক্সেস করতে পারছি - তারমানে আমরা এখানে
ref
কে একটাplain javascript object
এর মতো ব্যাবহার করতে পারছি ref.current
এর ভ্যালু আমরা কম্পোনেন্ট এর কোন রেন্ডারিং এ ব্যাবহার করিনি। আমরা শুধুমাত্র এলার্টে দেখিয়েছি।
এই বিষয়গুলোই মূলত মনে রাখতে হবে আমাদের useRef
ব্যবহারের ক্ষেত্রে।
Storing a Reference or values in ref ( ref এর মদ্ধে কোন রেফারেন্স বা ভ্যালু স্টোর করে রাখা)
আমরা useRef
এর current
প্রপার্টির মধ্যে কোন রেফারেন্স বা ভ্যালু স্টোর করে রাখতে পারি, এবং সেটা রিয়াক্টের স্টেট চেঞ্জ হলেও কম্পোনেন্ট সেই ভ্যালু টা ধরে রাখবে। অর্থাৎ আমরা ref
কে কম্পোনেন্ট এর এমন একটা স্টোরেজ হিসাবে ব্যাবহার করতে পারি যা, আমদের স্টেট চেঞ্জ হয়ে কম্পোনেন্ট যতবার রি-রেন্ডার হোক না কেন,আমাদের সেই স্টোর করা ডাটা হারাবেনা।
চলুন একটা উদাহরণের মাধ্যমে বুঝি। আমরা একটা Stop watch বানাবো এবং আমরা যখন Stop watch টা Start করবো তখন থেকে যখন আমরা Stop Watch টা বন্ধ করবো এর মাঝে কত সময় গিয়েছে সেটা UI তে দেখাবোঃ
import { useState } from "react";
export default function Stopwatch() {
const [startTime, setStartTime] = useState(null); //যখন আমরা Start বাটনে ক্লিক করলাম সেই টাইম টা আমরা স্টেট দিয়ে ম্যানেজ করছি
const [now, setNow] = useState(null); // Start করার পর বর্তমান পর্যন্ত কত সময় পার হয়েছে সেটা আমরা এর মাধ্যমে কেলকুলেট করবো।
function handleStart() {
// এখনে আমারে কাউন্টার টা Start করলাম
setStartTime(Date.now());
setNow(Date.now());
setInterval(() => {
// প্রতি ১০ মিলিসেকন্ড পর পর আমরা Stop Watch এর ভ্যালু স্টেট দিয়ে আপডেট করছি
setNow(Date.now());
}, 10);
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>Start</button>
</>
);
}
এখন আমাদের Stop Watch টাকে Stop করা লাগবে, Stop করতে হলে আমাদেরকে interval
টাকে clear
করতে হবে clearInterval(intervalReference)
দিয়ে। কিন্তু আমদেরকে তো interval
টাকে clear
করতে হলে interval
এর রেফারেন্সটাকে দরকার হবে, কিন্তু এখানে তো আমাদের interval
টা আছে একটা handler
এর ফাংশনের ভিতরে এবং প্রতি ১০ মিলিসেকন্ড পরপর টা রি-রেন্ডার ট্রিগার করছে, তাই আমরা স্বাভাবিকভাবেই clearInterval
করতে চাইলে সেই interval
এর রেফারেন্স টা পাবোনা। তাই এখেত্রে আমাদেরকে হেল্প করবেuseRef
হুক।
আমরা ref.current
এর মদ্ধ্যে সেই interval
এর রেফারেন্সটাকে স্টোর করবো এবং তাকে দিয়ে clearInterval
কল করবো।
এখানে প্রশ্ন আসতে পারে যে কেন আমদেরকে এই অবস্থায় interval
এর রেফারেন্সটাকে ref
এর মধ্যেই স্টোর করতে হবে, আমরা জাভাস্ক্রিপ্ট এর কোন ভ্যারিয়বল এ কেন রাখতে পারবোনা ?
দেখুন আমরা প্রথম interval
সেট করেছি handleStart
নামে একটা হ্যান্ডেলার ফাংশনের ভিতরে। এখন এই ফাংশনের ভিতরে যদি আমরা এই interval
টাকে কোন ভ্যারিয়েবল এ রাখি, তাহলে আমরা যখন Stop
করতে চাইবো তখন handleStop
ফাংশনের ভিতরে তো ওই ভ্যারিয়েবল টাকে এক্সেস করতে পারবোনা। কেননা তখন ওই ভেরিয়েবল এর স্কোপ তো শুধুমাত্র ওই handleStart
ফাংশনের ভিতরের জায়গা। তাই স্বাভাবিকভাবেই আমরা এটাকে কোন ফাংশনের লোকাল ভ্যরিয়েবল হিসেবে রেখে আবার অন্য কোন ফাংশনের ভিতর থেকে এক্সেস করতে পারবোনা।
আবার আমরা যদি গ্লোবাল স্কোপে কোন ইম্পটি ভ্যারিয়েবল ডিক্লেয়ার করে সেখানে interval
এর রেফারেন্সটাকে সেট করে handleStop
ফাংশনের ভিতরে এক্সেস করতে চাইতাম সেটাও পারতাম না, কেননা যখন আমরা Stop Watch টাকে Start করবো তখন থেকে প্রতি ১০ মিলি সেকন্ড পর পর কম্পোনেন্ট রি-রেন্ডার হচ্ছে, আর প্রতিবার রি-রেন্ডারে কম্পোনেন্ট একদম ফ্রেশ হয়ে রান হচ্ছে, তাই আমরা যখন কোন ইম্পটি ভ্যরিয়েবল ডিক্লেয়ার করবো তখন প্রতিটা রি-রেন্ডারে সেটা ইম্পটিই থাকবে, তাই আমরা handleStop
হেন্ডেলারের ভিতরেও যদি সেই ভ্যারিয়েবল্টাকে ব্যাবহার করি, তাহলে প্রতিবার ইম্পটি পাওয়ার কারনে আমাদের লজিক কাজ করবেনা।
এজন্যই আমরা এটাকে রেফারেন্সে রেখে কাজ করবো। কেননা রেফারেন্সটা আমাদের গ্লোবাল স্কোপে আছে, আর রেফারেন্স এর {current}
প্রপার্টিতে আমরা যেইটা সেট করবো সেটা কম্পোনেন্ট যতবার রি-রেন্ডার নেক না কেন, তা সে মনে রাখতে পারবে,বা রেফারেন্স টা ধরে রাখবে।
আর তাই আমরা এই সিচুয়েশনে একটা স্টোরেজ হিসেবে ref
কে ব্যাবহার করবো।
যহেতু IntervalId আমদের রেন্ডারিং এর মাঝে ব্যাবহার হয়না,তাই আমরা IntervalId কে স্টোর করার জন্য useRef
ব্যাবহার করতে পারি।
import { useState, useRef } from "react";
export default function Stopwatch() {
const ref = useRef(null);
const [startTime, setStartTime] = useState(null); //যখন আমরা Start বাটনে ক্লিক করলাম সেই টাইম টা আমরা স্টেট দিয়ে ম্যানেজ করছি
const [now, setNow] = useState(null); // Start করার পর বর্তমান পর্যন্ত কত সময় পার হয়েছে সেটা আমরা এর মাধ্যমে কেলকুলেট করবো।
function handleStart() {
// এখনে আমারে কাউন্টার টা Start করলাম
setStartTime(Date.now());
setNow(Date.now());
ref.current = setInterval(() => {
// এখানে ref.current এর মদ্ধ্যে interval কে স্টোর করা হয়েছে
// প্রতি ১০ মিলিসেকন্ড পর পর আমরা Stop Watch এর ভ্যালু স্টেট দিয়ে আপডেট করছি
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(ref.current); // এখানে সেই interval এর রেফারেন্স ধরে clearInterval() কল করা হয়েছে।
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</>
);
}
Differences between refs and state (state এবং ref এর মদ্ধ্যে পার্থক্য )
রিয়াক্টের useState
এবং useRef
প্রায় সেম, তবে useState
এর একটা setter function
থাকে যার মাধ্যমে স্টেট চেঞ্জ করা হয় কিন্তু useRef
এর কোন setter function
থাকেনা। বরং এটা একটা অবজেক্ট রিটার্ন করে এবং তার current
নামে একটা প্রপার্টি থাকে,যা চেঞ্জ করার মাধ্যমে ref
এর ভ্যালু চেঞ্জ করা হয়।
চলুন একটু বিস্তারিত পার্থক্য দেখে নেই।
refs | state |
---|---|
useRef একটা ইনিশিয়াল ভ্যালু নেয় এবং রিটার্ন করে একটা অবজেক্ট,এবং সেই অবজেক্ট এর current নামক প্রপার্টির কাছে ইনিশিয়াল ভ্যালুটা থাকে | useState একটা ইনিশিয়াল ভ্যালু নেয়, এবং রিটার্ন করে একটা state ভ্যারিয়েবল এবং একটা setter function |
ভ্যালু চেঞ্জ হলেও useRef রি-রেন্ডার ট্রিগার করেনা। | ভ্যালু আপডেট করলেই রি-রেন্ডার ট্রিগার করে |
useRef এর ভ্যালু কে ইচ্ছামতো Muted করা যায়। | useState এর State এর ভ্যালু Muted করা যায়না |
useRef এর current ভ্যালু কম্পোনেন্টের রেন্ডারিং এ ব্যাবহার করা যায়না | যেকোন সময় স্টেট এর ভায়লুকে রিড করা যায় |
When to use ref (কখন ref ব্যাবহার করা উচিত)
সাধারণত আমরা খুব বেশি একটা useRef
ব্যাবহার করবোনা। কিন্তু যখন আমাদের রিয়াক্টের কনট্রোলের বাইরে গিয়ে কোন এক্সটারনাল API সাথে সাথে কাজ করবো এবং তা আমদের কম্পোনেন্ট এর রেন্ডারিং এর কোন এফেক্ট ফেলবেনা। তখন আমরা useRef
ব্যাবহার করবো।
- Storing timeout / interval Ids (টাইমাউট আইডি বা ইন্টারভ্যাল আইডি স্টোর করে রাখার জন্য)
- Storing and manipulating DOM elements (DOM elements কে Stor করা এবং মডিফাই করার জন্য)
- Storing other objects that aren’t necessary to calculate the JSX (যেকোণ ধরনের অবজেক্ট স্টোর করা যেগুলো JSX এ রেন্ডার করার প্রয়োজন নাই)
Best practices for refs
useRef
ব্যাবহার করার জন্য আমদের দুইটা প্রিন্সিপল খুব ভালো করে মানতে হবে,
- **Treat refs as an escape hatch - রিয়াক্ট এ
ref
কে আমদের একটাescape hatch
হিসেবে ব্যবাহর করতে হবে। অর্থাৎ যখন আমরা ব্রাউজারের কোন এক্সটার্নাল API এর সাথে কাজ করবো তখন আমরাuseRef
ব্যবাহার করতে পারি। ** - Don’t read or write ref.current during rendering - আমরা রিয়াক্ট এর রেন্ডারিং এ
useRef
ব্যবহার করতে পারবোনা