State আপডেটস এর একটি ক্রম সারিবদ্ধ করা

State ভেরিয়েবল সেট করা হলে আরেকটি রেন্ডারকে সারিবদ্ধ করবে। কিন্তু পরের রেন্ডারকে সারিবদ্ধ করার আগে, কখনো কখনো আপনি হয়তো ভ্যালুতে অনেকগুলো অপারেশন করতে চাইতে পারেন। এটা করতে, React কিভাবে state আপডেট গুলোকে ব্যাচ করে তা বুঝতে পারবেন।

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

  • “ব্যাচিং” কি এবং React কিভাবে এটা ব্যবহার করে অনেকগুলো state আপডেটসকে প্রক্রিয়া করে।
  • একটি সারিতে একই state ভেরিয়েবলে কীভাবে বেশ কয়েকটি আপডেট প্রয়োগ করবেন।

React state আপডেটসকে ব্যাচিং করে

আপনি হয়তো অনুমান করতে পারেন যে, “+3” বাটনে ক্লিক করার পর, বাটনটি কাউন্টার কে তিন বার বৃদ্ধি করবে কারণ এটা এই setNumber(number + 1) ফাংশনকে ৩ বার কল করেঃ

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>
    </>
  )
}

তবে, আপনি হয়তো আগের অধ্যায় থেকে মনে করতে পারেন, প্রতিটি রেন্ডারের state এর মানগুলো স্থায়ী, সুতরাং প্রথম ইভেন্ট হ্যান্ডলারের ভিতরে number এর মান সর্বদাই 0 হবে, আপনি যতবারই setNumber(1) ফাংশনটি কল করুন না কেনঃ

setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);

কিন্তু এখানে অন্য একটি ফ্যাক্টর কাজ করে। React আপনার state আপডেটগুলো প্রসেসিং করার আগে, ইভেন্ট হ্যান্ডলারের সকল কোড রান করা পর্যন্ত অপেক্ষা করে। এই কারণে সকল setNumber() ফাংশন কল করার পরে, শুধুমাত্র তখনি রি-রেন্ডার ঘটে।

এটি আপনাকে একজন ওয়েটারের রেস্টুরেন্টে অর্ডার নেওয়ার কথা মনে করিয়ে দিতে পারে। একজন ওয়েটার আপনার প্রথম খাবারের কথা বলার সাথে সাথে রান্নাঘরে দৌড়ে যায় না! এর বদলে, তারা আপনাকে আপনার অর্ডারটি শেষ করতে দেয়, আপনাকে এতে পরিবর্তন করতে দেয় এবং এমনকি টেবিলে থাকা অন্য লোকেদের কাছ থেকে অর্ডার নেয়।

An elegant cursor at a restaurant places and order multiple times with React, playing the part of the waiter. After she calls setState() multiple times, the waiter writes down the last one she requested as her final order.

Illustrated by Rachel Lee Nabors

এটি আপনাকে একাধিক state ভেরিয়েবল আপডেট করতে দেয়।—এমনকি একাধিক কম্পোনেন্টস থেকেও—অনেক রি-রেন্ডারস ট্রিগার করা ছাড়াই। কিন্তু এর মানে হল যে UI আপডেট করা হবে না যতক্ষণ না আপনার ইভেন্ট হ্যান্ডলার থাকা কোনো কোড সম্পূর্ণ না হয়। এই আচরণ, ব্যাচিং নামেও পরিচিত, এটা আপনার react অ্যাপকে আরও দ্রুত চালায়। এটি বিভ্রান্তিকর “অর্ধ-সমাপ্ত” রেন্ডার কে এড়িয়ে যায় যেখানে শুধুমাত্র কিছু ভেরিয়েবল আপডেট করা হয়েছে।

ক্লিকের মত, একাধিক ইচ্ছাকৃত ইভেন্ট জুড়ে react ব্যাচ করে না—প্রতিটি ক্লিক আলাদাভাবে পরিচালনা করা হয়। নিশ্চিন্ত থাকুন যে react শুধুমাত্র তখনই ব্যাচিং করে যখন এটি করা সাধারণত নিরাপদ। এটি নিশ্চিত করে যে, উদাহরণস্বরূপ, যদি প্রথম বাটন ক্লিকে একটি ফর্ম নিষ্ক্রিয় করে, দ্বিতীয় ক্লিকটি আবার সাবমিট দিবে না।

পরবর্তী রেন্ডারের আগে একই state একাধিকবার আপডেট করা

এটা একটি বিরল ব্যবহার, কিন্তু যদি আপনার ভাল লাগে তাহলে পরবর্তী রেন্ডারের আগে একই state একাদিকবার আপডেট করতে পারেন, setNumber(number + 1) এর মতো করে পরবর্তী state এর মান পাস করার পরিবর্তে, আপনি একটা ফাংশন পাস করতে পারেন যেটা সারিতে থাকা আগেরটির উপর ভিত্তি করে পরবর্তী state গণনা করে, উদাহরণস্বরূপ setNumber(n => n + 1)। এটি react কে প্রতিস্থাপনের পরিবর্তে “state এর মান দিয়ে কিছু করতে” বলে।

এখন কাউন্টার বৃদ্ধি করার চেষ্টা করুনঃ

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(n => n + 1);
        setNumber(n => n + 1);
        setNumber(n => n + 1);
      }}>+3</button>
    </>
  )
}

এখানে, n => n + 1 একটি আপডেটার ফাংশন। যখন আপনি এটিকে state setter এ পাস করেনঃ

১। ইভেন্ট হ্যান্ডলারের অন্যান্য সমস্ত কোড চালানোর পরে এই ফাংশনটি প্রক্রিয়া করার জন্য react সারিবদ্ধ করে। ২। পরবর্তী রেন্ডারের সময়, React সারির মধ্য দিয়ে যায় এবং আপনাকে চূড়ান্ত আপডেটেড state দেয়।

setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);

ইভেন্ট হ্যান্ডলার চালানোর সময় কোডের এই লাইনগুলির মাধ্যমে react কীভাবে কাজ করে তা এখানে বলা হল:

১। setNumber(n => n + 1): n => n + 1 একটি ফাংশন। React এটিকে একটি সারিতে যোগ করে। ২। setNumber(n => n + 1): n => n + 1 একটি ফাংশন। React এটিকে একটি সারিতে যোগ করে। ৩। setNumber(n => n + 1): n => n + 1 একটি ফাংশন। React এটিকে একটি সারিতে যোগ করে।

আপনি যখন পরবর্তী রেন্ডারের সময় useState কল করেন, তখন react সারির মধ্য দিয়ে যায়। আগের number এর state ছিল 0, তাই ‘n’ আর্গুমেন্ট হিসাবে প্রথম আপডেটার ফাংশনে react একটাকে পাস করে। তারপর react আপনার পূর্ববর্তী আপডেটার ফাংশনের রিটার্নের মান নেয় এবং এটিকে পরবর্তী আপডেটারকে n হিসাবে প্রেরণ করে, ইত্যাদিঃ

queued updatenreturns
n => n + 100 + 1 = 1
n => n + 111 + 1 = 2
n => n + 122 + 1 = 3

চূড়ান্ত ফলাফল হিসাবে react 3 কে স্টোর এবং useState এ রিটার্ন করে।

এই কারণে উপরের উদাহরণে “+3” ক্লিক করলে মানটি 3 দ্বারা সঠিকভাবে বৃদ্ধি পায়।

State এর মান প্রতিস্থাপন করার পর আপনি যদি এটিকে আবার আপডেট করেন তাহলে কি হয়

এই ইভেন্ট হ্যান্ডলার সম্পর্কে কি ভাবেন? আপনি কি মনে করেন number এর মান পরবর্তী রেন্ডারে কি হবে?

<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
}}>
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setNumber(n => n + 1);
      }}>Increase the number</button>
    </>
  )
}

এই ইভেন্ট হ্যান্ডলার react কে কী করতে বলে তা এখানে দেখানো হলঃ

১। setNumber(number + 5): number হয় 0, তাই setNumber(0 + 5)। React তার সারিতে 5 দিয়ে প্রতিস্থাপন করে” যোগ করে। ২। setNumber(n => n + 1): n => n + 1 একটি আপডেটার ফাংশন। React তার সারিতে সেই আপডেটার ফাংশন কে যোগ করে।

পরবর্তী রেন্ডারের সময়, React state এর সারির মধ্য দিয়ে যায়ঃ

queued updatenreturns
5 দিয়ে প্রতিস্থাপন”0 (অব্যবহৃত)5
n => n + 155 + 1 = 6

চূড়ান্ত ফলাফল হিসাবে React 6 সঞ্চয় করে এবং useState এ রিটার্ন করে।

খেয়াল করুন

আপনি হয়তো লক্ষ্য করেছেন যে setState(5) আসলে setState(n => 5) এর মতো কাজ করে, কিন্তু n অব্যবহৃত!

State আপডেট করার পরে যদি এটিকে কে আবার প্রতিস্থাপন করেন তাহলে কি হয়

চলেন আরো একটি উদাহরণ দিয়ে চেষ্টা করি। আপনি কি মনে করেন number এর মান পরবর্তী রেন্ডারে কি হবে?

<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
setNumber(42);
}}>
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setNumber(n => n + 1);
        setNumber(42);
      }}>Increase the number</button>
    </>
  )
}

এই ইভেন্ট হ্যান্ডলারটি চালানোর সময় কোডের এই লাইনগুলির মাধ্যমে react কীভাবে কাজ করে তা এখানে দেখানো হলঃ

১। setNumber(number + 5): number হয় 0, তাই setNumber(0 + 5)। React তার সারিতে 5 দিয়ে প্রতিস্থাপন করে” যোগ করে। ২। setNumber(n => n + 1): n => n + 1 একটি আপডেটার ফাংশন। React তার সারিতে সেই আপডেটার ফাংশন কে যোগ করে। ৩। setNumber(42): React তার সারিতে 42 দিয়ে প্রতিস্থাপন করে” যোগ করে।

পরবর্তী রেন্ডারের সময়, React state এর সারির মধ্য দিয়ে যায়ঃ

queued updatenreturns
5 দিয়ে প্রতিস্থাপন”0 (অব্যবহৃত)5
n => n + 155 + 1 = 6
42 দিয়ে প্রতিস্থাপন”6 (অব্যবহৃত)42

তারপর react চূড়ান্ত ফলাফল হিসাবে 42 সঞ্চয় করে এবং useState এ রিটার্ন করে।

সংক্ষেপে, আপনি setNumber state সেটারে কী পাস করছেন তা আপনি কীভাবে ভাবতে পারেন তা এখানে দেয়া হলঃ

  • একটি আপডেটার ফাংশন (যেমন n => n + 1) সারিতে যোগ করা হয়।
  • অন্য যেকোনো মান (যেমন 5 সংখ্যা) সারিতে “5 দিয়ে প্রতিস্থাপন করে” যোগ করে, যা ইতিমধ্যে সারিবদ্ধ আছে তা বাতিল করে।

ইভেন্ট হ্যান্ডলার সম্পূর্ণ হওয়ার পরে, React পুনরায় একটি রেন্ডার ট্রিগার করবে। পুনরায় রেন্ডার করার সময়, React সারিটি প্রক্রিয়া করবে। আপডেটার ফাংশনগুলি রেন্ডারিংয়ের সময় চলে, তাই আপডেটার ফাংশনগুলি অবশ্যই বিশুদ্ধ হতে হবে এবং ফলাফলটি শুধুমাত্র রিটার্ন করতে হবে। তাদের ভিতর থেকে state সেট করার চেষ্টা করবেন না বা অন্যান্য পার্শ্ব প্রতিক্রিয়া চালাবেন না। Strict মোডে, React আপনার ভুল খুঁজে পেতে সাহায্য করার জন্য প্রতিটি আপডেটার ফাংশন দুবার চালাবে (কিন্তু দ্বিতীয় ফলাফলটি বাতিল করবে)।

নামকরণের কনভেনশন

সংশ্লিষ্ট state ভেরিয়েবলের প্রথম অক্ষর দ্বারা আপডেটার ফাংশন এর আর্গুমেন্টের নাম দেওয়া হয় প্রচলিতভাবেঃ

setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);

আপনি যদি আরও শব্দবহুল কোড পছন্দ করেন, তাহলে আরেকটি সাধারণ নিয়ম হল setEnabled(enabled => !enabled) এটার মতো করে পূর্ণ state ভেরিয়েবল নাম পুনরাবৃত্তি করা, অথবা setEnabled(prevEnabled => !prevEnabled) এর মতো করে একটি প্রিফিক্স ব্যবহার করা।

পুনরালোচনা

  • সেটিং state বিদ্যমান রেন্ডারে ভেরিয়েবলকে পরিবর্তন করে না, কিন্তু এটি একটি নতুন রেন্ডারের অনুরোধ করে।
  • ইভেন্ট হ্যান্ডলারদের চালানো শেষ হওয়ার পরে react state আপডেটগুলি প্রক্রিয়া করে। একে ব্যাচিং বলে।
  • একটি ইভেন্টে কিছু state একাধিকবার আপডেট করতে, আপনি setNumber(n => n + 1) আপডেটার ফাংশন ব্যবহার করতে পারেন।

Challenge 1 of 2:
Request কাউন্টারটি ঠিক করুন

আপনি একটি আর্ট মার্কেটপ্লেস অ্যাপে কাজ করছেন যা ব্যবহারকারীকে একই সময়ে একটি আর্ট আইটেমের জন্য একাধিক অর্ডার জমা দিতে দেয়। প্রতিবার ব্যবহারকারী “Buy” বাটনে ক্লিক করলে, “Pending” কাউন্টারটি এক দ্বারা বৃদ্ধি করা উচিত। তিন সেকেন্ড পরে, “Pending” কাউন্টারটি হ্রাস করা উচিত এবং “Completed” কাউন্টারটি বৃদ্ধি করা উচিত।

তবে, “Pending” কাউন্টারটি যেভাবে চাচ্ছি সে অনুযায়ী আচরণ করে না। আপনি যখন “Buy” বাটনে ক্লিক করেন, তখন তা কমে -1 হয়ে যায় (যা সম্ভব নয়!)। এবং যদি আপনি দ্রুত দুইবার ক্লিক করেন, উভয় কাউন্টার অপ্রত্যাশিতভাবে আচরণ করে বলে মনে হচ্ছে।

কেন এটা ঘটবে? উভয় কাউন্টার ঠিক করুন।

import { useState } from 'react';

export default function RequestTracker() {
  const [pending, setPending] = useState(0);
  const [completed, setCompleted] = useState(0);

  async function handleClick() {
    setPending(pending + 1);
    await delay(3000);
    setPending(pending - 1);
    setCompleted(completed + 1);
  }

  return (
    <>
      <h3>
        Pending: {pending}
      </h3>
      <h3>
        Completed: {completed}
      </h3>
      <button onClick={handleClick}>
        Buy     
      </button>
    </>
  );
}

function delay(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}