ReactNext
React-documentationBasics-2

2.5 State as a Snapshot

Welcome to React Next Documentation Bangla

State as a Snapshot

React-এ স্টেট ভেরিয়েবল সাধারণ জাভাস্ক্রিপ্ট ভেরিয়েবলের মতো মনে হলেও, এটি একেবারেই সেরকম কাজ করে না। চল এবার একটু গভীরভাবে দেখে নিই কেন এটা আলাদা এবং এর পেছনের কাজগুলো কীভাবে ঘটে।

Rendering Takes a Snapshot in Time:

React-এর একটি মজার বৈশিষ্ট্য হলো, প্রতিবার রেন্ডার হওয়ার সময় এটি একটি নির্দিষ্ট snapshot নেয় এবং তা ব্যবহার করে। যখনই কোনো স্টেট আপডেট হয়, তখন React সেই কম্পোনেন্টকে পুনরায় রেন্ডার করে এবং নতুন রেন্ডারে গিয়ে সেই স্টেটের আপডেটেড ভ্যালু পাওয়া যায়। এর মানে হলো, যখন আমরা কোনো স্টেট পরিবর্তন করি, তখনি আমরা পরিবর্তিত ভ্যালুটি সাথে সাথে ব্যবহার করতে পারি না। ভ্যালুটির পরিবর্তন কার্যকর হবে পরের রেন্ডারে।

চলুন এই বিষয়টি একটি উদাহরণের মাধ্যমে বুঝে নিই।

উদাহরণ ১: ইনস্ট্যান্ট স্টেট চেঞ্জ হয় না

import { useState } from "react";
export default function App() {
  const [number, setNumber] = useState(1);
 
  function handleClick() {
    setNumber(number + 5);
    console.log(number); // কী হতে পারে এখানে আউটপুট?
  }
 
  return <button onClick={handleClick}>Increment the number</button>;
}

এখানে যদি তুমি বোতামটি ক্লিক করো, তবে console.log(number) এ কি দেখতে পাও? হয়তো মনে হতে পারে, আউটপুট হবে 6 (যেহেতু তুমি 1 এর সাথে 5 যোগ করেছো)। কিন্তু না, আউটপুট আসবে 1। কেন? কারণ, React এর স্টেট সরাসরি ইনপ্লেসে পরিবর্তিত হয় না। যখন তুমি setNumber(number + 5) কল করো, তখন React এর স্টেট পরিবর্তন হবে, কিন্তু সেই পরিবর্তন পরের রেন্ডারে প্রভাব ফেলবে, বর্তমানে না। তাই রেন্ডার ফিনিশ না হওয়া পর্যন্ত আমরা স্টেটের নতুন ভ্যালু দেখতে পাই না।

উদাহরণ ২: একই ফাংশনে একাধিকবার স্টেট আপডেট

import { useState } from "react";
 
export default function Counter() {
  const [number, setNumber] = useState(0);
 
  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(number + 1);
          setNumber(number + 1);
          setNumber(number + 1);
        }}
      >
        +3
      </button>
    </>
  );
}

এখানে বোতামটি চাপলে তিনবার setNumber(number + 1) কল করা হয়েছে। অনেকেই আশা করতে পারেন যে আউটপুট হবে 3, কিন্তু আউটপুট আসবে 1। কেন? কারণ, React প্রতিবার স্টেট আপডেট করার সময় সে একই রেন্ডারের snapshot এর উপর ভিত্তি করে কাজ করে। এখানে তিনবার setNumber(number + 1) কল করা হলেও, প্রতিবার সে একই পুরানো স্টেট (0) থেকে কাজ করছে। তাই তিনবার 0 + 1 করা হয়েছে এবং শেষে ভ্যালুটি 1-তেই এসে দাঁড়িয়েছে।

উদাহরণ ৩: Async কোড এবং স্টেট

export default function Counter() {
  const [number, setNumber] = useState(0);
 
  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(number + 5);
          setTimeout(() => {
            alert(number); // কী হতে পারে এলার্টের আউটপুট?
          }, 3000);
        }}
      >
        +5
      </button>
    </>
  );
}

এবারের উদাহরণে আমরা setTimeout ব্যবহার করছি। বোতাম চাপার সাথে সাথে setNumber(number + 5) কল করা হয়েছে, এবং ৩ সেকেন্ড পর আমরা এলার্টে দেখব কী হয়েছে। এখন মনে হতে পারে যে ৩ সেকেন্ড পর যখন এলার্ট দেখাবে, সেখানে আপডেটেড ভ্যালু 5 আসা উচিত। কিন্তু না, আসলে এলার্টে দেখাবে 0। কেন?

React যখন setNumber(number + 5) কল করে, তখন সেটি রেন্ডার ফিনিশ হওয়ার আগেই আগের ভ্যালু (0) এর একটি snapshot নেয়। setTimeout এর মধ্যে যে number ব্যবহার হচ্ছে সেটিও সেই পুরানো snapshot অনুযায়ী চলে। তাই পরের রেন্ডার হওয়ার আগে যেকোনো async কোড আগের রেন্ডারের snapshot-এর উপর ভিত্তি করে কাজ করবে। চল এবার আমরা আরও কিছু উদাহরণ দেখি যা React-এর স্টেট আপডেট এবং রেন্ডারিং এর কাজগুলো আরও পরিষ্কারভাবে তুলে ধরবে।

উদাহরণ ৪: ফাংশনের মধ্যে একাধিক স্টেট আপডেট কিভাবে একসাথে কাজ করে

ধরো, তুমি একই ফাংশনের মধ্যে একাধিকবার স্টেট আপডেট করতে চাইছো। তবে প্রশ্ন হলো, প্রতিটি setState কল কীভাবে আচরণ করে? এর জন্য নিচের উদাহরণটা দেখা যাক।

import { useState } from "react";
 
export default function Counter() {
  const [count, setCount] = useState(0);
 
  function handleClick() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    console.log("Count:", count);
  }
 
  return (
    <>
      <h1>{count}</h1>
      <button onClick={handleClick}>Increase Count</button>
    </>
  );
}

এখন তুমি যদি এই কোড রান করো এবং বোতামে ক্লিক করো, তুমি কি ভাবতে পারো কনসোলে আউটপুট কী হবে? যদিও তিনবার setCount(count + 1) কল করা হয়েছে, আউটপুট আসবে 0। আর প্রতিবার setCount আগের snapshot অনুযায়ী কাজ করবে।

সমাধান: ফাংশনাল আপডেট ব্যবহার করা

React এই সমস্যার সমাধানের জন্য ফাংশনাল আপডেট প্যাটার্ন প্রস্তাব করে, যেখানে আমরা প্রিভিয়াস স্টেটের উপর ভিত্তি করে নতুন স্টেট সেট করতে পারি। এটি বিশেষভাবে সহায়ক যখন একাধিকবার স্টেট আপডেট করার দরকার হয়।

import { useState } from "react";
 
export default function Counter() {
  const [count, setCount] = useState(0);
 
  function handleClick() {
    setCount((prevCount) => prevCount + 1);
    setCount((prevCount) => prevCount + 1);
    setCount((prevCount) => prevCount + 1);
    console.log("Count after click:", count);
  }
 
  return (
    <>
      <h1>{count}</h1>
      <button onClick={handleClick}>Increase Count</button>
    </>
  );
}

এবার বোতামে ক্লিক করলে তুমি দেখতে পাবে আউটপুট হবে 3। কারণ ফাংশনাল আপডেট প্যাটার্ন ব্যবহার করার মাধ্যমে আমরা প্রত্যেকবার প্রিভিয়াস স্টেটের উপর ভিত্তি করে নতুন স্টেট গণনা করছি, তাই প্রতিটি setCount কল সঠিকভাবে প্রিভিয়াস স্টেট ধরে কাজ করবে।

উদাহরণ ৫: অ্যাসিঙ্ক্রোনাস স্টেট আপডেট আরেকভাবে কাজ করে

স্টেট আপডেট অ্যাসিঙ্ক্রোনাস হলেও, কিছু ক্ষেত্রে আমরা চাই, আমাদের কাজটি হয়ে যাওয়ার পরেই স্টেট আপডেট হোক। এজন্য React স্টেট আপডেট এবং অ্যাসিঙ্ক্রোনাস ফাংশনগুলোর কাজগুলো বোঝা দরকার।

import { useState } from "react";
 
export default function AsyncCounter() {
  const [count, setCount] = useState(0);
 
  async function handleClick() {
    await new Promise((resolve) => setTimeout(resolve, 1000)); // 1 সেকেন্ড অপেক্ষা করো
    setCount(count + 1);
    console.log("Count:", count);
  }
 
  return (
    <>
      <h1>{count}</h1>
      <button onClick={handleClick}>Increase Count After 1s</button>
    </>
  );
}

এখানে যদি তুমি বোতামে ক্লিক করো, console.log("Count:", count) দেখাবে 0, যদিও আমরা এক সেকেন্ড অপেক্ষা করেছি। কারণ এখানে রেন্ডারিং এর আগের snapshot এর ভ্যালু ব্যবহার হয়েছে। পরবর্তী রেন্ডারে count আপডেট হবে, তবে তা অ্যাসিঙ্ক্রোনাস ফাংশনের ভেতরে হচ্ছে বলে তা দেখা যাবে না।

উদাহরণ ৬: স্টেট আপডেট নির্ভরশীলতা

কখনও কখনও আমাদের একাধিক স্টেট নির্ভরশীল থাকে। যখন একটির আপডেট আরেকটির উপর নির্ভর করে, তখন সঠিকভাবে স্টেট আপডেট নিশ্চিত করার জন্য কিছু কৌশল দরকার।

import { useState } from "react";
 
export default function DependentCounter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);
 
  function handleClick() {
    setCount(count + step);
  }
 
  return (
    <>
      <h1>Count: {count}</h1>
      <h1>Step: {step}</h1>
      <button onClick={handleClick}>Increase Count by Step</button>
      <button onClick={() => setStep(step + 1)}>Increase Step</button>
    </>
  );
}

এখানে count আপডেটের উপর নির্ভর করছে step এর ভ্যালু। তাই আমরা যখন স্টেপ বাড়াবো, count এর ইনক্রিমেন্ট ততটাই হবে। বোতামে একবার ক্লিক করলে count আপডেট হবে step এর ভ্যালু অনুযায়ী।

উপসংহার:

এই উদাহরণগুলোতে আমরা দেখলাম React কীভাবে স্টেট আপডেট করে এবং কীভাবে স্টেট চেঞ্জগুলো রেন্ডারিং-এর সময়কার snapshot-এর উপর ভিত্তি করে পরিচালিত হয়। React-এর স্টেট আপডেট সবসময়ই asynchronous এবং এটি প্রতিবারের রেন্ডারিং সাইকেলকে ম্যানেজ করে। তাই স্টেট আপডেট করতে হলে, বিশেষ করে যদি তা পরপর কয়েকবার করা হয় বা অ্যাসিঙ্ক্রোনাস কোনো কাজের মাধ্যমে করা হয়, তা বুঝে-শুনে করতে হয়।

এই উদাহরণগুলো যদি তুমি ভালোভাবে অনুশীলন করো, তাহলে React-এর স্টেট ম্যানেজমেন্ট আরও পরিষ্কার হবে।

On this page