Ref ব্যবহার করে DOM ম্যানিপুলেশন

React স্বয়ংক্রিয়ভাবে DOM আপডেট করে যেন এটি আপনার রেন্ডার আউটপুটের সাথে মিলে যায়, তাই আপনার কম্পোনেন্টের প্রায় সময়ই দরকার হবে না এটা পরিবর্তন করার। তবে, মাঝে মাঝে আপনার React পরিচালিত DOM elements এর প্রয়োজন হতে পারে—উদাহরণস্বরূপ, একটি node কে ফোকাস করতে, স্ক্রল করতে, অথবা এর আকার এবং অবস্থান পরিমাপ করতে। React এ এই ধরণের কাজ করার জন্য কোনো বিল্ট-ইন উপায় নেই, তাই আপনার DOM node এর ref প্রয়োজন হবে।

যা যা আপনি শিখবেন

  • কীভাবে React এর পরিচালিত একটি DOM নোড ref এট্রিবিউট ব্যবহার করে অ্যাক্সেস করবেন
  • কীভাবে ref JSX এট্রিবিউট useRef হুকের সাথে সম্পর্কিত।
  • কীভাবে অন্য একটি কম্পোনেন্টের DOM নোড অ্যাক্সেস করবেন
  • কোন কোন ক্ষেত্রে React এর পরিচালিত একটি DOM নোড পরিবর্তন করা নিরাপদ

একটা নোডকে ref পর্যন্ত নিয়ে যাওয়া

React এর পরিচালিত একটি DOM নোড অ্যাক্সেস করতে, প্রথমে, useRef হুকটি ইমপোর্ট করুনঃ

import { useRef } from 'react';

এর পর, একে ব্যবহার করে আপনার কম্পোনেন্টের মধ্যে একটি ref ডিক্লেয়ার করেনঃ

const myRef = useRef(null);

সবশেষে, যেই JSX ট্যাগের জন্য আপনি DOM নোড চাচ্ছেন, সেটায় একে ref এট্রিবিউট হিসেবে পাস করে দিনঃ

<div ref={myRef}>

useRef হুক একটি অবজেক্ট রিটার্ন করে যার একটি মাত্র প্রপার্টি থাকে current নামে। প্রাথমিকভাবে, myRef.current হবে null। যখন React এই <div> এর জন্য একটি DOM node তৈরি করে, React এই নোডের একটি রেফারেন্স myRef.current-এ রাখবে। তারপর আপনি আপনার event handlers থেকে এই DOM node এ অ্যাক্সেস করতে পারেন এবং এর উপর ডিফাইন করা বিল্ট-ইন browser APIs ব্যবহার করতে পারেন।

// আপনি যেকোন ব্রাউজার API ব্যবহার করতে পারেন। উদাহরণস্বরূপঃ
myRef.current.scrollIntoView();

উদাহরণঃ একটা টেক্সট ইনপুটে ফোকাস করা

এই উদাহরণে, বাটনে ক্লিক করলে ইনপুট ফোকাস হবেঃ

import { useRef } from 'react';

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

এটা করার জন্যঃ

  1. useRef হুক ব্যবহার করে inputRef ডিক্লেয়ার করুন।
  2. <input ref={inputRef}> হিসেবে একে পাস করে দিন। এটা React কে বলবে যেন এই <input>এর DOM নোড inputRef.currentএর মধ্যে রাখা হয়।
  3. handleClick ফাংশনে, inputRef.current থেকে ইনপুট DOM নোড রিড করুন এবং inputRef.current.focus() দিয়ে এর উপর focus() কল করুন।
  4. handleClick ইভেন্ট হ্যান্ডলারটি onClick এর সাহায্যে <button> এ পাঠিয়ে দিন।

যদিও DOM ম্যানিপুলেশন এর জন্যই refs এর ব্যবহার সবচেয়ে বেশি হয়, useRef হুক React এর বাইরে অন্যান্য জিনিস সংরক্ষণ করার জন্য ব্যবহৃত হতে পারে, যেমন টাইমার ID। State এর মতোই, রেন্ডারে মধ্যবর্তী সময়ে ref ঠিকঠাক থাকে। Refs হল state variables এর মত যেগুলি সেট করার সময় re-render ট্রিগার হয় না। ref সম্বন্ধে পড়ুন ref এর সাহায্যে ভ্যালু রেফারেন্সিং অংশে।

উদাহরণঃ কোন এলিমেন্ট পর্যন্ত স্ক্রল করা

আপনি একটি কম্পোনেন্টে একাধিক ref রাখতে পারেন। এই উদাহরণে, তিনটি চিত্রের একটি ক্যারাসেল আছে। প্রতিটি বাটন ব্রাউজারে ওই ছবির সাথে সম্পর্কিত DOM নোডে scrollIntoView() মেথড কল করে ছবিটিকে মাঝামাঝি আনে।

import { useRef } from 'react';

export default function CatFriends() {
  const firstCatRef = useRef(null);
  const secondCatRef = useRef(null);
  const thirdCatRef = useRef(null);

  function handleScrollToFirstCat() {
    firstCatRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function handleScrollToSecondCat() {
    secondCatRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function handleScrollToThirdCat() {
    thirdCatRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  return (
    <>
      <nav>
        <button onClick={handleScrollToFirstCat}>
          Tom
        </button>
        <button onClick={handleScrollToSecondCat}>
          Maru
        </button>
        <button onClick={handleScrollToThirdCat}>
          Jellylorum
        </button>
      </nav>
      <div>
        <ul>
          <li>
            <img
              src="https://placekitten.com/g/200/200"
              alt="Tom"
              ref={firstCatRef}
            />
          </li>
          <li>
            <img
              src="https://placekitten.com/g/300/200"
              alt="Maru"
              ref={secondCatRef}
            />
          </li>
          <li>
            <img
              src="https://placekitten.com/g/250/200"
              alt="Jellylorum"
              ref={thirdCatRef}
            />
          </li>
        </ul>
      </div>
    </>
  );
}

গভীরভাবে জানুন

কীভাবে একটি ref callback ব্যবহার করে ref এর একটা তালিকা ম্যানেজ করবেন

উপরের উদাহরণগুলিতে, ref এর একটি পূর্বনির্ধারিত সংখ্যা রয়েছে। যদিও, মাঝে মাঝে আপনার তালিকার প্রতিটি আইটেমের জন্য একটি ref প্রয়োজন হতে পারে, এবং আপনি জানবেন না আপনার কতগুলো থাকবে। এরকম কিছু কাজ করবে নাঃ

<ul>
{items.map((item) => {
// কাজ করে না!
const ref = useRef(null);
return <li ref={ref} />;
})}
</ul>

এর কারণ হুক শুধুমাত্র আপনার কম্পোনেন্টের শীর্ষ-স্তরে ডাকতে হবে। আপনি একটি লুপে, একটি কন্ডিশনে, বা একটি map() কলের ভিতরে useRef ডাকতে পারবেন না।

একে এডিয়ে যাবার একটি সম্ভাব্য উপায় হল প্যারেন্ট এলিমেন্টে একটা মাত্র ref নিয়ে যাওয়া এবং তারপরে DOM ম্যানিপুলেশন পদ্ধতিগুলি যেমন querySelectorAll ব্যবহার করে এটি থেকে individual child node “খুঁজে” বের করা। যদিও, এটি অত্যন্ত নাজুক এবং যদি আপনার DOM কাঠামো পরিবর্তন হয় তবে এটি ভেঙে যেতে পারে।

অন্য একটি সমাধান হলো ref এট্রিবিউটে একটি ফাংশন পাস করা। এটি ref callback. নামে পরিচিত। React যখন ref সেট করার সময় হবে তখন এটি আপনার ref callback কে DOM নোড দিয়ে কল করবে, এবং যখন এটি সাফ করার সময় হবে তখন null দিয়ে ডাকবে। এটি আপনাকে আপনার নিজের একটি array বা একটি Map বজায় রাখতে দেয়, এবং এর মাধ্যমে আপনি তার ইনডেক্স বা কোন ধরণের ID দ্বারা যেকোনো ref অ্যাক্সেস করতে পারেন।

এই উদাহরণটি দেখায় যে আপনি এই পদ্ধতিটি কীভাবে ব্যবহার করে একটি দীর্ঘ তালিকায় যেকোনো নোডে স্ক্রল করতে পারেনঃ

import { useRef } from 'react';

export default function CatFriends() {
  const itemsRef = useRef(null);

  function scrollToId(itemId) {
    const map = getMap();
    const node = map.get(itemId);
    node.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function getMap() {
    if (!itemsRef.current) {
      // প্রথম ব্যবহারে ম্যাপ ইনিশিয়ালাইজ করুন
      itemsRef.current = new Map();
    }
    return itemsRef.current;
  }

  return (
    <>
      <nav>
        <button onClick={() => scrollToId(0)}>
          Tom
        </button>
        <button onClick={() => scrollToId(5)}>
          Maru
        </button>
        <button onClick={() => scrollToId(9)}>
          Jellylorum
        </button>
      </nav>
      <div>
        <ul>
          {catList.map(cat => (
            <li
              key={cat.id}
              ref={(node) => {
                const map = getMap();
                if (node) {
                  map.set(cat.id, node);
                } else {
                  map.delete(cat.id);
                }
              }}
            >
              <img
                src={cat.imageUrl}
                alt={'Cat #' + cat.id}
              />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

const catList = [];
for (let i = 0; i < 10; i++) {
  catList.push({
    id: i,
    imageUrl: 'https://placekitten.com/250/200?image=' + i
  });
}

এউ উদাহরণে, itemsRef একটা DOM নোডও রাখে না। বরং এটা আইটেম ID থেকে DOM নোডের একটা ম্যাপ রাখে। তালিকার প্রতিটি আইটেমে ref callback ম্যাপের আপডেটের বিষয়টা ঠিক রাখে।

<li
key={cat.id}
ref={node => {
const map = getMap();
if (node) {
// ম্যাপে যোগ করুন
map.set(cat.id, node);
} else {
// ম্যাপ থেকে সরিয়ে ফেলুন
map.delete(cat.id);
}
}}
>

এটা আপনাকে ম্যাপ থেকে DOM নোড পৃথকভাবে রিড করতে দেয়।

অন্য একটি কম্পোনেন্টের DOM নোড অ্যাক্সেস করা

আপনি যখন এমন একটা বিল্ট-ইন কম্পোনেন্টে রেফ বসান যা <input /> এর মত একটি ব্রাউজার এলিমেন্ট আউটপুট হিসেবে দেয়, React সেই ref এর current হিসেবে প্রপার্টি সম্পর্কিত DOM নোড (যেমন ব্রাউজারের প্রকৃত <input />) সেট করে দেবে।

তবে, আপনি যদি আপনার নিজের কম্পোনেন্টে একটা ref বসাতে চান, যেমন <MyInput />, তাহলে স্বাভাবিকভাবে আপনি null পাবেন। এখানে বিষয়টা দেখায় এমন একটি উদাহরণ দেখানো হল। খেয়াল করুন বাটনে ক্লিক করলে ইনপুটে ফোকাস হয় না।

import { useRef } from 'react';

function MyInput(props) {
  return <input {...props} />;
}

export default function MyForm() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

আপনি যেন ঝামেলাটা খেয়াল করেন, সেজন্য React কনসোলে একটি এরর দেখিয়ে দেয়ঃ

Console
সতর্কতাঃ ফাংশন কম্পোনেন্টে ref দেওয়া যায় না। এমন একটি ref এ অ্যাক্সেসের চেষ্টা বিফল হবে। আপনি কি React.forwardRef() ব্যবহার করতে চেয়েছিলেন?

এটি ঘটে কারণ স্বাভাবিকভাবে React কম্পোনেন্টকে অন্যান্য কম্পোনেন্টের DOM নোড অ্যাক্সেস করতে দেয় না। তার নিজের children দের জন্যও নয়! এটি ইচ্ছাকৃত। Ref এক ধরণের escape hatch যা খুব কম ব্যবহার করা উচিত। ম্যানুয়ালি অন্য কম্পোনেন্টের DOM নোড পরিবর্তন করা আপনার কোডকে আরও নাজুক বানিয়ে ফেলে।

এর পরিবর্তে, যে কম্পোনেন্টগুলি তাদের DOM নোড উন্মুক্ত করতে চায় তাদেরকে এই আচরণ আয়ত্ব করে নিতে হবে। একটি কম্পোনেন্ট নির্দিষ্ট করতে পারে যে এটি তার ref তার একটি সন্তানের কাছে “ফরোয়ার্ড” করে। এটা কিভাবে MyInput forwardRef API ব্যবহার করতে পারে তা দেখানো হলো:

const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});

এটা কাজ করে এই ভাবেঃ

  1. <MyInput ref={inputRef} /> React কে বলে corresponding DOM নোড inputRef.current এর মধ্যে রাখতে। কিন্তু, এই সিদ্ধান্তটাMyInput কম্পোনেন্টের উপর নির্ভর করে যে সে এটা করবে কি না—স্বাভাবিকভাবে সে এটা করে না।
  2. MyInput কম্পোনেন্টটা forwardRef ব্যবহার করে ডিক্লেয়ার করা হয়। এটা উপরের inputRef কে দ্বিতীয় ref আর্গুমেন্ট হিসেবে নেওয়ার সিদ্ধান্ত নেয়, যা props এর পরে ডিক্লেয়ার করা হয়।
  3. MyInput যেই ref টা পায় সেটা নিজেই এর ভিতরকার <input> এ পাস করে দেয়।

এখন বাটন ক্লিক করে ইনপুট ফোকাস হচ্ছে ঠিকঠাকভাবেঃ

import { forwardRef, useRef } from 'react';

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

সাধারণত ডিজাইন সিস্টেমে, বাটন, ইনপুট এবং এরকম কিছু অন্যান্য নিম্ন-স্তরের কম্পোনেন্টগুলির জন্য তাদের ref তাদের DOM নোডে ফরওয়ার্ড করা একটি সাধারণ প্যাটার্ন। অন্যদিকে, ফরম, তালিকা, বা পেইজের সেকশনের মতো উচ্চ-স্তরের কম্পোনেন্টগুলি সাধারণত DOM নোড প্রকাশ করবে না যাতে DOM কাঠামোতে আকস্মিক নির্ভরতা কমে যায়।

গভীরভাবে জানুন

API এর একাংশ imperative handle এর সাহায্যে উন্মুক্ত করা

উপরের উদাহরণে, MyInput মূল DOM ইনপুট এলিমেন্টটি প্রকাশ করে। এটি প্যারেন্ট কম্পোনেন্টকে এটির উপর focus() কল করার সুযোগ দেয়। যদিও, এটি প্যারেন্ট কম্পোনেন্টকে অন্য একটা কাজ সুযোগ করে দেয় - উদাহরণস্বরূপ, এর CSS স্টাইল পরিবর্তন করা। হঠাৎ হঠাৎ, আপনি হয়তো exposed functionality সীমাবদ্ধ করতে চাইবেন। আপনি এটি useImperativeHandle এর সাহায্যে করতে পারেনঃ

import {
  forwardRef, 
  useRef, 
  useImperativeHandle
} from 'react';

const MyInput = forwardRef((props, ref) => {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    // শুধুমাত্র ফোকাস উন্মুক্ত করুন আর কিছু না
    focus() {
      realInputRef.current.focus();
    },
  }));
  return <input {...props} ref={realInputRef} />;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

এখানে, MyInput এর মধ্যে realInputRef আসল ইনপুট DOM নোডটি ধারণ করে। যদিও, useImperativeHandle React-কে বলে যেন প্যারেন্ট কম্পোনেন্টের জন্য ref এর মান হিসাবে আপনার নিজের বিশেষ একটা অবজেক্ট সরবরাহ করবে। তাই Form কম্পোনেন্টের ভেতরে inputRef.current শুধু মাত্র focus মেথডটি আছে। এই ক্ষেত্রে, ref “handle” DOM নোড নয়, বরং useImperativeHandle কলের ভেতরে আপনি যে কাস্টম অবজেক্ট তৈরি করেন সেটা।

When React attaches the refs

React এ, প্রতিটি আপডেট দুটি পর্যায়ে ভাগ করা হয়ঃ

  • render এর সময়, React আপনার কম্পোনেন্টগুলি কল করে যেন পর্দায় কী থাকা উচিত তা নির্ধারণ করতে পারে।
  • commit এর সময়, React DOM এ পরিবর্তনগুলি প্রয়োগ করে।

সাধারণত, আপনি রেন্ডারিং এর সময় ref গুলিতে অ্যাক্সেস করতে চান না। DOM নোডগুলি ধারণ করা ref এর জন্য এটিও প্রযোজ্য। প্রথম রেন্ডারের সময়, DOM নোডগুলি এখনও তৈরি হয়নি, সুতরাং ref.current হবে null। আর আপডেট রেন্ডারিং এর সময়, DOM নোডগুলি এখনও আপডেট হয়নি। সুতরাং এখনো তাদের read করার সময় হয় নাই।

React কমিটের সময় ref.current সেট করে। DOM আপডেট করার আগে, React প্রভাবিত ref.current মানগুলিকে null সেট করে। DOM আপডেট করার পরে, React তা অবিলম্বে সম্পর্কিত DOM নোডগুলিতে সেট করে।

সাধারণত, আপনি ইভেন্ট হ্যান্ডলারগুলি থেকে ref গুলি অ্যাক্সেস করবেন। আপনি যদি কোনও ref এর সাথে কিছু করতে চান, কিন্তু এটি করার জন্য কোনও নির্দিষ্ট ইভেন্ট না থাকে, আপনার একটি Effect প্রয়োজন হতে পারে। আমরা পরবর্তী সেকশন গুলিতে Effects সম্পর্কে আলোচনা করব।

গভীরভাবে জানুন

flushSync ব্যবহার করে সিঙ্ক্রোনাস ভাবে state update flush করা

এরকম কোড বিবেচনা করুন, যা একটি নতুন todo যোগ করে এবং স্ক্রিনটি তালিকার শেষ চাইল্ডের দিকে স্ক্রল করে। লক্ষ্য করুন কিভাবে, কিছু কারণে, এটি সবসময় শেষে যোগ করা todo এর ঠিক আগের একটিতে স্ক্রল করেঃ

import { useState, useRef } from 'react';

export default function TodoList() {
  const listRef = useRef(null);
  const [text, setText] = useState('');
  const [todos, setTodos] = useState(
    initialTodos
  );

  function handleAdd() {
    const newTodo = { id: nextId++, text: text };
    setText('');
    setTodos([ ...todos, newTodo]);
    listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });
  }

  return (
    <>
      <button onClick={handleAdd}>
        Add
      </button>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <ul ref={listRef}>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </>
  );
}

let nextId = 0;
let initialTodos = [];
for (let i = 0; i < 20; i++) {
  initialTodos.push({
    id: nextId++,
    text: 'Todo #' + (i + 1)
  });
}

এই দুটি লাইনে সমস্যাটি আছেঃ

setTodos([ ...todos, newTodo]);
listRef.current.lastChild.scrollIntoView();

React এ, state আপডেট কিউ করে রাখা হয়। সাধারণত, আপনি এইটাই চাইবেন। তবে, এখানে এটা সমস্যা করে কারণ setTodos DOM কে সাথে সাথে আপডেট করে না। সুতরাং যখন আপনি স্ক্রল করে তালিকের শেষ এলিমেন্টে যাবেন, তখনো todo যোগ করা হয় নাই। এই কারণে স্ক্রলিং সব সময়ে এক আইটেমের হিসেবে “পিছিয়ে থাকে।“

এই সমস্যাটা সমাধানের জন্য, আপনি React কে DOM সিঙ্ক্রোনাসভাবে আপডেট (“flush”) করতে বাধ্য করতে পারেন। এটা করার জন্য react-dom থেকে flushSync ইমপোর্ট করুন এবং state আপডেটকে ঘিরে ফেলুন একটা flushSync কলের মধ্যেঃ

flushSync(() => {
setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView();

এটা React কে নির্দেশ করবে যেন flushSync দিয়ে ঘেরা কোড চলার সাথে সাথে সিঙ্ক্রোনাস ভাবে DOM আপডেট হয়ে যায়। ফলাফলস্বরূপ, আপনি যতক্ষণে শেষ TODO তে স্ক্রল করে যাবেন তার আগেই এটা DOM এর মধ্যে থাকবে।

import { useState, useRef } from 'react';
import { flushSync } from 'react-dom';

export default function TodoList() {
  const listRef = useRef(null);
  const [text, setText] = useState('');
  const [todos, setTodos] = useState(
    initialTodos
  );

  function handleAdd() {
    const newTodo = { id: nextId++, text: text };
    flushSync(() => {
      setText('');
      setTodos([ ...todos, newTodo]);      
    });
    listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });
  }

  return (
    <>
      <button onClick={handleAdd}>
        Add
      </button>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <ul ref={listRef}>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </>
  );
}

let nextId = 0;
let initialTodos = [];
for (let i = 0; i < 20; i++) {
  initialTodos.push({
    id: nextId++,
    text: 'Todo #' + (i + 1)
  });
}

ref ব্যবহার করে DOM ম্যানিপুলেশনের ক্ষেত্রে যা যা মেনে চলা উচিত

Ref হচ্ছে এক ধরণের escape hatch। আপনার শুধুমাত্র “React এর বাইরে পদক্ষেপ নিতে” হলে এর ব্যবহার করা উচিত। এই ধরনের সাধারণ উদাহরণগুলির মধ্যে রয়েছে ফোকাস করা, স্ক্রোল অবস্থান বা ব্রাউজার API গুলি কল করা যা React প্রকাশ করে না।

আপনি যদি ফোকাসিং এবং স্ক্রোলিং এর মতো অবিনাশী কাজগুলিতে স্থাপিত থাকেন, তবে আপনার কোনও সমস্যা হওয়ার কথা নয়। যদিও, আপনি যদি ম্যানুয়ালি DOM পরিবর্তন করার চেষ্টা করেন, তবে আপনি React এর করা পরিবর্তনের সাথে বিরোধিতা ঘটানোর ঝুঁকি নিতে পারেন।

এই সমস্যাটা ব্যাখ্যা করতে, এই উদাহরণে একটা ওয়েলকাম মেসেজ এবং দুটি বাটন আছে। প্রথম বাটনটি conditional rendering এবং state ব্যবহার করে এর উপস্থিতি বদল করে, যেমন আপনি স্বাভাবিকভাবে React এ করেন। দ্বিতীয় বাটনটি remove() DOM API ব্যবহার করে একে জোর করে DOM থকে সরিয়ে React এর নিয়ন্ত্রণের বাইরে পাঠিয়ে দেয়।

কয়েকবার “Toggle with setState” চাপার চেষ্টা করেন। মেসেজটা হারিয়ে যাবার কথা আবার আসার কথা। এরপরে “Remove from the DOM” এ চাপ দিন। এটা একে জোর করে সরিয়ে ফেলবে। সবশেষে, “Toggle with setState” চাপ দিনঃ

import { useState, useRef } from 'react';

export default function Counter() {
  const [show, setShow] = useState(true);
  const ref = useRef(null);

  return (
    <div>
      <button
        onClick={() => {
          setShow(!show);
        }}>
        Toggle with setState
      </button>
      <button
        onClick={() => {
          ref.current.remove();
        }}>
        Remove from the DOM
      </button>
      {show && <p ref={ref}>Hello world</p>}
    </div>
  );
}

আপনি নিজে DOM এলিমেন্ট সরিয়ে ফেলবার পরে, setState ব্যবহার করে একে আবার দেখানোর চেষ্টা করলে সিস্টেম ক্র্যাশ করবে। এর কারণ আপনি DOM বদলে ফেলেছেন, এবং React জানে না এর পরে একে কীভাবে পরিচালনা করবে।

React দিয়ে পরিচালিত DOM নোড পরিবর্তন এড়িয়ে চলবেন। যেসব এলিমেন্ট React পরিচালনা করে সেগুলোর পরিবর্তন, চাইল্ড যোগ করা বা চাইল্ড বাদ দেয়ার কারণে ভিজ্যুয়াল ফলাফলে অসামাঞ্জস্য দেখা যেতে পারে বা উপরের মত ক্র্যাশ করতে পারে।

তবে, এর মানে এটা না যে আপনি এটা একদমই করতে পারবেন না। এর জন্য দরকার সতর্কতা। আপনি নিরাপদভাবে DOM এর সেই অংশগুলো পরিবর্তন করতে পারবেন যেগুলো React এর আপডেট করার কোন কারণ নেই উদাহরণস্বরূপ, যদি JSX এ কোন <div> সবসময় খালি থাকে, এর চিলড্রেন তালিকায় হাত দেয়ার কোন কারণ থাকবে না React এর। সুতরাং, সেখানে নিজ থেকে এলিমেন্ট যুক্ত করা বা বাদ দেওয়াটা নিরাপদ।

পুনরালোচনা

  • Ref একটি সাধারণ ধারণা, কিন্তু বেশিরভাগ সময় আপনি DOM এলিমেন্ট হোল্ড করার জন্য এর ব্যবহার করবেন।
  • আপনি <div ref={myRef}> পাস করার মাধ্যমে React কে নির্দেশ করবেন যেন myRef.current এর মধ্যে একটি DOM নোড ঢুকানো হয়।
  • সাধারণত, আপনি ref ব্যবহার করবেন এমন কাজ করার জন্য যা ধ্বংসাত্মক না। যেমন ফোকাস করা, স্ক্রল করা বা DOM এলিমেন্টের measure করা।
  • একটা কম্পোনেন্ট এর DOM নোড স্বাভাবিকভাবে উন্মুক্ত করে না। আপনি forwardRef ব্যবহার করার মাধ্যমে এবং একটা নির্দিষ্ট নোডে দ্বিতীয় ref আর্গুমেন্ট পাঠিয়ে দেবার মাধ্যমে DOM নোড উন্মুক্ত করার সিদ্ধান্ত নিতে পারেন।
  • React পরিচালনা করে এমন DOM নোড পরিবর্তন করা এড়িয়ে চলুন।
  • আপনি যদি React এর পরিচালিত DOM নোড পরিবর্তন করে, তাহলে এমন অংশে পরিবর্তনটা আনেন যেটা React এর আপডেট করার কোন কারণ নেই।

চ্যালেঞ্জ 1 / 4:
ভিডিও চালু এবং বন্ধ করুন

এই উদাহরণে, ভিডিও চালু এবং বন্ধ থাকা অবস্থার সুইচ করার জন্য বাটন একটি state ভ্যারিয়েবল পরিবর্তন করে। তবে, ভিডিও আসলেই বন্ধ বা চালু করার জন্য, state ভ্যারিয়েবল পরিবর্তন করা যথেষ্ট না। আপনাকে <video> এর DOM এলিমেন্টে play() এবং pause() কল করতে হবে। এর সাথে একটি ref যোগ করুন, এবং নিশ্চিত করুন যে বাটনটা কাজ করছে।

import { useState, useRef } from 'react';

export default function VideoPlayer() {
  const [isPlaying, setIsPlaying] = useState(false);

  function handleClick() {
    const nextIsPlaying = !isPlaying;
    setIsPlaying(nextIsPlaying);
  }

  return (
    <>
      <button onClick={handleClick}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <video width="250">
        <source
          src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
          type="video/mp4"
        />
      </video>
    </>
  )
}

একটা অতিরিক্ত চ্যালেঞ্জের জন্য, যদি ব্যবহারকারী ভিডিওতে রাইট ক্লিক করে বিল্ট-ইন ব্রাউজার মিডিয়া কন্ট্রোল ব্যবহার করে ভিডিও চালু করে দেয়, সেটার সাথে “Play” বাটন sync রাখুন। আপনি ভিডিওর onPlay এবং onPause listen করতে চাইতে পারেন।