Reactive Accelerator
React Js
React Escape Hatches
4.1 - Referenceing Values with Refs

এই পার্টে আমরা সর্বপ্রথম 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 এর ব্যাপারে স্বচ্ছ ধারনা পেয়ে যাবো।

খেয়াল করুনঃ

  1. useRef এর ref.cuurent ভ্যালু পরিবর্তন করা হয়েছে একটা হ্যান্ডেলারের ভিতর।
  2. useRefএরref.cuurentভ্যালু পরিবর্তন করার জন্য কোন রকমsetter function` কল করা হয়নি
  3. setter function কল করা হয়নি বলে কম্পোনেন্ট রি-রেন্ডার করবেনা
  4. কম্পোনেন্ট রি-রেন্ডার না করা সত্ত্বেও ref এর ভ্যালু পরিবর্তন করার সাথে সাথেই পরের লাইনেই আমরা আপডেটেড ভ্যালু এক্সেস করতে পারছি
  5. তারমানে আমরা এখানে ref কে একটা plain javascript object এর মতো ব্যাবহার করতে পারছি
  6. 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 এর ভ্যালু চেঞ্জ করা হয়।

চলুন একটু বিস্তারিত পার্থক্য দেখে নেই।

refsstate
useRef একটা ইনিশিয়াল ভ্যালু নেয় এবং রিটার্ন করে একটা অবজেক্ট,এবং সেই অবজেক্ট এর current নামক প্রপার্টির কাছে ইনিশিয়াল ভ্যালুটা থাকেuseState একটা ইনিশিয়াল ভ্যালু নেয়, এবং রিটার্ন করে একটা state ভ্যারিয়েবল এবং একটা setter function
ভ্যালু চেঞ্জ হলেও useRef রি-রেন্ডার ট্রিগার করেনা।ভ্যালু আপডেট করলেই রি-রেন্ডার ট্রিগার করে
useRef এর ভ্যালু কে ইচ্ছামতো Muted করা যায়।useState এর State এর ভ্যালু Muted করা যায়না
useRefএর current ভ্যালু কম্পোনেন্টের রেন্ডারিং এ ব্যাবহার করা যায়নাযেকোন সময় স্টেট এর ভায়লুকে রিড করা যায়

When to use ref (কখন ref ব্যাবহার করা উচিত)

সাধারণত আমরা খুব বেশি একটা useRef ব্যাবহার করবোনা। কিন্তু যখন আমাদের রিয়াক্টের কনট্রোলের বাইরে গিয়ে কোন এক্সটারনাল API সাথে সাথে কাজ করবো এবং তা আমদের কম্পোনেন্ট এর রেন্ডারিং এর কোন এফেক্ট ফেলবেনা। তখন আমরা useRef ব্যাবহার করবো।

  1. Storing timeout / interval Ids (টাইমাউট আইডি বা ইন্টারভ্যাল আইডি স্টোর করে রাখার জন্য)
  2. Storing and manipulating DOM elements (DOM elements কে Stor করা এবং মডিফাই করার জন্য)
  3. Storing other objects that aren’t necessary to calculate the JSX (যেকোণ ধরনের অবজেক্ট স্টোর করা যেগুলো JSX এ রেন্ডার করার প্রয়োজন নাই)

Best practices for refs

useRef ব্যাবহার করার জন্য আমদের দুইটা প্রিন্সিপল খুব ভালো করে মানতে হবে,

  1. **Treat refs as an escape hatch - রিয়াক্ট এ ref কে আমদের একটা escape hatch হিসেবে ব্যবাহর করতে হবে। অর্থাৎ যখন আমরা ব্রাউজারের কোন এক্সটার্নাল API এর সাথে কাজ করবো তখন আমরা useRef ব্যবাহার করতে পারি। **
  2. Don’t read or write ref.current during rendering - আমরা রিয়াক্ট এর রেন্ডারিং এ useRef ব্যবহার করতে পারবোনা