State সংরক্ষণ এবং রিসেট করা

State কম্পোনেন্টের মধ্যে ভিন্ন রাখা হয়। React একটি UI ট্রি এর ভিতরে কোন State কোন কম্পোনেন্টের সঙ্গে সংযোগস্থলে পরিচয় রেখে থাকে। আপনি নিয়ন্ত্রণ করতে পারেন যখন অবস্থা সংরক্ষণ করতে হবে এবং যখন রি-রেন্ডার এর মধ্যে তা পুনরায় সেট করতে হবে। 

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

  • React কীভাবে কম্পোনেন্ট স্ট্রাকচারগুলি “দেখে”
  • React কোন সময়ে State সংরক্ষণ বা রিসেট করতে সিলেক্ট করে
  • React এ কীভাবে কম্পোনেন্টের অবস্থা রিসেট করতে বাধ্য করা যায়
  • React এ State সংরক্ষণ কি ভাবে প্রভাহিত হয় কীস (keys) এবং প্রকার (types) এর জন্য

UI ট্রি

ব্রাউজার অনেক ধরনের ট্রি কাঠামো ব্যবহার করে থাকে UI মডেল করার জন্য। DOM HTML উপাদানগুলি প্রতিষ্ঠা করে, CSSOM একইভাবে CSS এর জন্য করে। এখানে Accessibility tree নামক ট্রি আছে।

React সাধারণতঃ UI গড়ে তুলতে এবং পরিচালনা করতে ট্রি স্ট্রাকচার ব্যবহার করে। React JSX থেকে UI ট্রি তৈরি করে। তারপরে React DOM ব্রাউজারের DOM উপাদানগুলি আপডেট করে যাতে সেই UI ট্রির সাথে মিল খায়। (React Native এই ট্রিগুলি মোবাইল প্ল্যাটফর্মের উপাদানগুলির জন্য প্রতিষ্ঠান করে।)।

Diagram with three sections arranged horizontally. In the first section, there are three rectangles stacked vertically, with labels 'Component A', 'Component B', and 'Component C'. Transitioning to the next pane is an arrow with the React logo on top labeled 'React'. The middle section contains a tree of components, with the root labeled 'A' and two children labeled 'B' and 'C'. The next section is again transitioned using an arrow with the React logo on top labeled 'React'. The third and final section is a wireframe of a browser, containing a tree of 8 nodes, which has only a subset highlighted (indicating the subtree from the middle section).
Diagram with three sections arranged horizontally. In the first section, there are three rectangles stacked vertically, with labels 'Component A', 'Component B', and 'Component C'. Transitioning to the next pane is an arrow with the React logo on top labeled 'React'. The middle section contains a tree of components, with the root labeled 'A' and two children labeled 'B' and 'C'. The next section is again transitioned using an arrow with the React logo on top labeled 'React'. The third and final section is a wireframe of a browser, containing a tree of 8 nodes, which has only a subset highlighted (indicating the subtree from the middle section).

কম্পোনেন্ট থেকে শুরু করে, React একটি UI ট্রি তৈরি করে যা React DOM ব্যবহার করে ডম রেন্ডার করতে।

State ট্রির একটি অবস্থানের সাথে বাঁধা রয়েছে

যখন আপনি কোন কম্পোনেন্টকে স্টেট দিন, আপনি সম্প্রতি মনে করতে পারেন যে স্টেটটি কেবলমাত্র কম্পোনেন্টের “ভিতরে” বসে থাকে। কিন্তু স্টেটটি প্রদত্তক্ষেত্রে বাস করে যেমন রিয়েক্টে। রিয়েক্ট প্রতিটি স্টেট টুকরা সঠিক কম্পোনেন্ট সঙ্গে যুক্ত করে রাখে যেখানে ঐ কম্পোনেন্টটি UI ট্রি এর মধ্যে অবস্থান করে।

এখানে, কেবলমাত্র একটি <Counter /> JSX ট্যাগ আছে, কিন্তু এটি দুটি জায়গায় রেন্ডার হয়েছে:

import { useState } from 'react';

export default function App() {
  const counter = <Counter />;
  return (
    <div>
      {counter}
      {counter}
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

এখানে এগুলি কিভাবে ট্রি হিসেবে দেখায়:

Diagram of a tree of React components. The root node is labeled 'div' and has two children. Each of the children are labeled 'Counter' and both contain a state bubble labeled 'count' with value 0.
Diagram of a tree of React components. The root node is labeled 'div' and has two children. Each of the children are labeled 'Counter' and both contain a state bubble labeled 'count' with value 0.

React ট্রি

এগুলি দুটি আলাদা কাউন্টার কারণ প্রতিটি নিজস্ব অবস্থানে ট্রি এর মধ্যে রেন্ডার করা হয়েছে। আপনাকে সাধারণতঃ রিয়েক্ট ব্যবহার করার সময় এই অবস্থানগুলি চিন্তা করতে হয় না, তবে কিভাবে এটি কার্য করে তা বোঝার জন্য এটা কার্যকর হতে পারে।

React-এ, প্রদর্শিত প্রতিটি কম্পোনেন্টের জন্য একটি সম্পূর্ণ আলাদা State আছে। উদাহরণস্বরূপ, যদি আপনি পাশাপাশি দুটি Counter কম্পোনেন্টকে রেন্ডার করেন, তখন প্রতিটি কম্পোনেন্ট নিজস্ব, স্বতন্ত্র, score এবং hover states পাওয়া যাবে।

দুটি কাউন্টার একই সাথে ক্লিক করুন এবং দেখুন যে তারা একে অপরকে প্রভাবিত করে না:

import { useState } from 'react';

export default function App() {
  return (
    <div>
      <Counter />
      <Counter />
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

যেমন আপনি দেখতে পাচ্ছেন, একটি কাউন্টার আপডেট করা হলে, শুধুমাত্র সেই কম্পোনেন্টের স্টেটটি আপডেট হয়ছে:

Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 1. The state bubble of the right child is highlighted in yellow to indicate its value has updated.
Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 1. The state bubble of the right child is highlighted in yellow to indicate its value has updated.

স্টেট আপডেট করা

React ততোক্ষণ State ধরে রাকবে যতক্ষণ একই পজিশনে একই কম্পোনেন্ট রেন্ডার করা হচ্ছে। এটা দেখতে, দুটি কাউন্টারের মান বাড়ানোর পর দ্বিতীয় কম্পোনেন্টটি সরানোর জন্য “দ্বিতীয় কাউন্টার রেন্ডার করুন” চেকবক্স আনচেক করুন, এবং তারপরে আবার সেইটি যুক্ত করতে আবার চেক করুন।

import { useState } from 'react';

export default function App() {
  const [showB, setShowB] = useState(true);
  return (
    <div>
      <Counter />
      {showB && <Counter />} 
      <label>
        <input
          type="checkbox"
          checked={showB}
          onChange={e => {
            setShowB(e.target.checked)
          }}
        />
        Render the second counter
      </label>
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

লক্ষ্য করুন যে মুহূর্তে আপনি দ্বিতীয় কাউন্টার রেন্ডার করা বন্ধ করেন, তখন ঐ কাউন্টারের স্টেট সম্পূর্ণভাবে অদৃশ্য হয়ে যায়। এটা হওয়ার কারণ React কম্পোনেন্টটি সরানোর সময় তার স্টেট ধ্বংস করে।

Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is missing, and in its place is a yellow 'poof' image, highlighting the component being deleted from the tree.
Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is missing, and in its place is a yellow 'poof' image, highlighting the component being deleted from the tree.

কোম্পোনেন্টটি মুছে ফেলা হল

যখন আপনি “দ্বিতীয় কাউন্টার রেন্ডার করুন” টিক চিহ্নতে টিক করেন, তখন একটি দ্বিতীয় Counter এবং এর স্টেটটি শূন্য থেকে শুরু করা হয় (score = 0) এবং এটি DOM-এ যুক্ত করা হয়।

Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The entire right child node is highlighted in yellow, indicating that it was just added to the tree.
Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The entire right child node is highlighted in yellow, indicating that it was just added to the tree.

কম্পোনেন্ট যোগ করা

React কম্পোনেন্টের State সংরক্ষণ করে রাখে ততক্ষণ পর্যন্ত যতোক্ষণ ঐ কম্পোনেন্টটি UI ট্রির মধ্যে নির্দিষ্ট অবস্থানে রেন্ডার হচ্ছে। যদি কোনও কম্পোনেন্ট সরানো হয় অথবা একই অবস্থানে একটি অন্য কম্পোনেন্ট রেন্ডার করা হয়, তবে React তার স্টেটটি বাতিল করে ফেলে।

একই কম্পোনেন্ট একই অবস্থানে থাকলে State সংরক্ষিত থাকে

এই উদাহরণে, দুটি আলাদা <Counter /> ট্যাগ আছে:

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <Counter isFancy={true} /> 
      ) : (
        <Counter isFancy={false} /> 
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

যখন আপনি চেকবক্স টিক বা সাফ করার সময়, কাউন্টারের স্টেট রিসেট হয় না। isFancy যদি is true সত্য বা false মিথ্যা, আপনি সবসময় একটি <Counter /> একটি প্রথম চাইল্‌ড্‌ হিসাবে পেতে পারেন যা রুট App কম্পোনেন্ট থেকে ফিরে আসা div থেকে প্রাপ্ত হয়েছে:

Diagram with two sections separated by an arrow transitioning between them. Each section contains a layout of components with a parent labeled 'App' containing a state bubble labeled isFancy. This component has one child labeled 'div', which leads to a prop bubble containing isFancy (highlighted in purple) passed down to the only child. The last child is labeled 'Counter' and contains a state bubble with label 'count' and value 3 in both diagrams. In the left section of the diagram, nothing is highlighted and the isFancy parent state value is false. In the right section of the diagram, the isFancy parent state value has changed to true and it is highlighted in yellow, and so is the props bubble below, which has also changed its isFancy value to true.
Diagram with two sections separated by an arrow transitioning between them. Each section contains a layout of components with a parent labeled 'App' containing a state bubble labeled isFancy. This component has one child labeled 'div', which leads to a prop bubble containing isFancy (highlighted in purple) passed down to the only child. The last child is labeled 'Counter' and contains a state bubble with label 'count' and value 3 in both diagrams. In the left section of the diagram, nothing is highlighted and the isFancy parent state value is false. In the right section of the diagram, the isFancy parent state value has changed to true and it is highlighted in yellow, and so is the props bubble below, which has also changed its isFancy value to true.

App এর স্টেট আপডেট করলেও Counter রিসেট হয় না কারণ Counter একই অবস্থানেই থাকে।

এটি একই কম্পোনেন্ট একই অবস্থানে তখন, সুতরাং React এর দৃষ্টিতে এটি একই কাউন্টার:

সতর্কতা

মনে রাখবেন, React এর জন্য গুরুত্বপূর্ণ জেএসএক্স মার্কআপ নয়, বরং ইউআই ট্রির মধ্যে অবস্থানটি গুরুত্বপূর্ণ! এই কম্পোনেন্টটি দুটি return ক্লজ আছে যেখানে if এর ভিতরে এবং বাইরে ভিন্ন সংখ্যক <Counter /> জেএসএক্স ট্যাগ আছে:

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  if (isFancy) {
    return (
      <div>
        <Counter isFancy={true} />
        <label>
          <input
            type="checkbox"
            checked={isFancy}
            onChange={e => {
              setIsFancy(e.target.checked)
            }}
          />
          Use fancy styling
        </label>
      </div>
    );
  }
  return (
    <div>
      <Counter isFancy={false} />
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

আপনি যদি চেকবক্স টিক করার পরে স্টেটটি রিসেট হবার আশা করেন, তবে এটা হয় না! এটা কারণে, এই দুটি <Counter /> ট্যাগ একই অবস্থানে রেন্ডার হয়। React জানে না আপনি কোন শর্তগুলি কোথায় রাখেন আপনার ফাংশনে। এটি শুধুমাত্র আপনি return করা ট্রি দেখতে পায়।

উভয় ক্ষেত্রেই App কম্পোনেন্ট <div> রিটার্ন করে যা <Counter /> একটি প্রথম চাইল্‌ড্‌ হিসাবে। রিয়েক্ট এর দৃষ্টিতে, এই দুটি কাউন্টার একই “ঠিকানায়” আছে: এটি মূল রুটের প্রথম চাইল্‌ড্‌-এর প্রথম চাইল্‌ড্‌। এটি রিয়েক্টের পূর্ববর্তী এবং পরবর্তী রেন্ডার মধ্যে তাদের মিলাতে সমান করে। এটি আপনি যদি আপনার লজিক কেমন বিন্যাস করুন না কেন।

একই অবস্থানে বিভিন্ন কম্পোনেন্টগুলি স্টেট রিসেট করে

এই উদাহরণে, চেকবক্স চিহ্নিত করলে <Counter> এর স্থানে <p> যুক্ত হবে:

import { useState } from 'react';

export default function App() {
  const [isPaused, setIsPaused] = useState(false);
  return (
    <div>
      {isPaused ? (
        <p>See you later!</p> 
      ) : (
        <Counter /> 
      )}
      <label>
        <input
          type="checkbox"
          checked={isPaused}
          onChange={e => {
            setIsPaused(e.target.checked)
          }}
        />
        Take a break
      </label>
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

এখানে, আপনি একই অবস্থানে বিভিন্ন ধরণের কম্পোনেন্ট পরিবর্তন করেন। প্রাথমিকভাবে, <div> এর প্রথম চাইল্‌ড্‌-টির মধ্যে একটি Counter ছিল। But when you swapped in a p, React removed the Counter from the UI tree and destroyed its state. কিন্তু p কে স্থানান্তরিত করার সময়, React কাউন্‌টারটিকে UI-ট্রি থেকে সরে নিয়েছে এবং এর স্টেট ধ্বংস করেছে।

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'Counter' containing a state bubble labeled 'count' with value 3. The middle section has the same 'div' parent, but the child component has now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'p', highlighted in yellow.
Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'Counter' containing a state bubble labeled 'count' with value 3. The middle section has the same 'div' parent, but the child component has now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'p', highlighted in yellow.

যখন Counter পরিবর্তন হয়ে p হয়, তখন Counter মুছে যায় এবং p যুক্ত হয়।

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'p'. The middle section has the same 'div' parent, but the child component has now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, highlighted in yellow.
Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'p'. The middle section has the same 'div' parent, but the child component has now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, highlighted in yellow.

পুনরায় পরিবর্তন করার সময়, p মুছে যায় এবং Counter যুক্ত হয়।

যখন একই অবস্থানে একটি নতুন কম্পোনেন্ট রেন্ডার করা হয়, তখন এর সম্পূর্ণ উপপাদ্যের স্টেট রিসেট হয়। এটি কার্যকর হওয়ার পদ্ধতি দেখতে একটি উদাহরণে, কাউন্টার সংখ্যা বাড়ান এবং তারপরে চেকবক্স চিহ্নিত করুন:

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <div>
          <Counter isFancy={true} /> 
        </div>
      ) : (
        <section>
          <Counter isFancy={false} />
        </section>
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

চেকবক্স ক্লিক করার সময় কাউন্টারের স্টেট রিসেট হয়। যদিও আপনি একটি Counter রেন্ডার করেন, কিন্তু div এর প্রথম চাইল্‌ড্‌ পরিবর্তন হয়, যা একটি section হয়ে যায়। যখন DOM থেকে চাইল্‌ড্‌ div সরানো হয়, তখন তার নীচের সমস্ত ট্রি (যথায়থ Counter এবং এর স্টেটসহ) ধ্বংস হয়ে যায়।

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'section', which has a single child labeled 'Counter' containing a state bubble labeled 'count' with value 3. The middle section has the same 'div' parent, but the child components have now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'div', highlighted in yellow, also with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, all highlighted in yellow.
Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'section', which has a single child labeled 'Counter' containing a state bubble labeled 'count' with value 3. The middle section has the same 'div' parent, but the child components have now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'div', highlighted in yellow, also with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, all highlighted in yellow.

section থেকে div এ পরিবর্তন হলে, section মুছে যায় এবং নতুন div যুক্ত হয়।

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'div', which has a single child labeled 'Counter' containing a state bubble labeled 'count' with value 0. The middle section has the same 'div' parent, but the child components have now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'section', highlighted in yellow, also with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, all highlighted in yellow.
Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'div', which has a single child labeled 'Counter' containing a state bubble labeled 'count' with value 0. The middle section has the same 'div' parent, but the child components have now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'section', highlighted in yellow, also with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, all highlighted in yellow.

পুনরায় পরিবর্তন করার সময়, div মুছে যায় এবং নতুন section যুক্ত হয়

একটি নির্দিষ্ট নিয়ম হিসাবে ধরা যায় যদি আপনি পুনরায় রেন্ডার মাধ্যমে স্টেটকে সংরক্ষণ রাখতে চান তবে আপনার ট্রির গঠনটি পুনরায় রেন্ডার থেকে একে অন্যকে “মিলানো” প্রয়োজন। যদি গঠন ভিন্ন হয়, তবে স্টেটটি ধ্বংস করা হয় কারণ React একটি কম্পোনেন্টকে ট্রি থেকে সরানোর সময় স্টেট ধ্বংস করে।

সতর্কতা

এটা হলো কারণ আপনাকে কম্পোনেন্ট ফাংশনের সংজ্ঞা নেস্ট করা উচিত নয়।

এখানে, MyTextField কম্পোনেন্ট ফাংশনটি MyComponent এর মধ্যে সংজ্ঞায়িত করা হয়েছে:

import { useState } from 'react';

export default function MyComponent() {
  const [counter, setCounter] = useState(0);

  function MyTextField() {
    const [text, setText] = useState('');

    return (
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
    );
  }

  return (
    <>
      <MyTextField />
      <button onClick={() => {
        setCounter(counter + 1)
      }}>Clicked {counter} times</button>
    </>
  );
}

বাটন ক্লিক প্রতিটি সময়ে, ইনপুট স্টেট মুছে যায়! এটি হলো কারণ প্রতিটি MyComponent রেন্ডারের জন্য একটি ভিন্ন MyTextField ফাংশন তৈরি হয়। আপনি একই অবস্থানে ভিন্ন কম্পোনেন্ট রেন্ডার করছেন, তাই React সমস্ত স্টেট রিসেট করে। এটি বাগ এবং কার্যক্ষমতা সমস্যার কারণ হয়। এই সমস্যা সমাধানের জন্য, সবসময় কম্পোনেন্ট ফাংশনগুলি শীর্ষ-স্তরে ঘোষণা করুন এবং তাদের সংজ্ঞায়িতি নেস্ট না করুন।

একই অবস্থানে স্টেট রিসেট করা

ডিফল্ট অবস্থানে থাকলে রিয়েক্ট কম্পোনেন্টের স্টেট সংরক্ষণ রাখে। সাধারণতঃ, এটি আপনি চান তাই এটি ডিফল্ট আচরণ হিসাবে সুসংগত। কিন্তু কখনও কখনও, আপনি কম্পোনেন্টের স্টেট রিসেট করতে চান হতে পারেন। একটি অ্যাপটি চিন্তা করুন যা দুটি খেলোয়াড় অপরপরের স্কোরগুলি পর্যালোচনা করতে দেয় প্রতিটি টার্নে:

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter person="Taylor" />
      ) : (
        <Counter person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

বর্তমানে, যখন আপনি খেলোয়াড় পরিবর্তন করেন, স্কোর সংরক্ষণ করা হয়। দুটি Counter একই অবস্থানে দেখা যায়, তাই রিয়েক্ট তাদের একই Counter হিসাবে ধরে নেয় যার প্রপার্টি person পরিবর্তন হয়েছে।

তবে ধারণাগতভাবে, এই অ্যাপে তাদের দুটি পৃথক কাউন্টার হওয়া উচিত। তারা ইউআইতে একই জায়গায় দেখা যাতে পারে, কিন্তু একটি টেলরের জন্য একটি কাউন্টার এবং অন্যটি সারাহের জন্য একটি কাউন্টার।

এখানে দুটি উপায় আছে যখন তাদের মধ্যে স্থানান্তর করা হয়, স্টেট রিসেট করতে:

  1. বিভিন্ন অবস্থানে কম্পোনেন্টগুলি রেন্ডার করুন
  2. প্রতিটি কম্পোনেন্টের জন্য একটি স্পষ্ট পরিচয় দিন সাধারিত করুন key দ্বারা

অপশন ১: কম্পোনেন্টটি বিভিন্ন অবস্থানে রেন্ডার করা

যদি আপনি এই দুটি Counter কে স্বাধীন রাখতে চান, তবে আপনি তাদের দুটি ভিন্ন অবস্থানে রেন্ডার করতে পারেন:

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA &&
        <Counter person="Taylor" />
      }
      {!isPlayerA &&
        <Counter person="Sarah" />
      }
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

  • প্রাথমিকভাবে, isPlayerAtrue হয়। সুতরাং, প্রথম অবস্থানে Counter স্টেট থাকে এবং দ্বিতীয়টি খালি থাকে।
  • আপনি “Next Player” বাটনটি চাপানোর পরে প্রথম অবস্থানটি খালি হয়ে যায় কিন্তু দ্বিতীয় অবস্থানটি এখন একটি Counter ধারণ করে।
Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'true'. The only child, arranged to the left, is labeled Counter with a state bubble labeled 'count' and value 0. All of the left child is highlighted in yellow, indicating it was added.
Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'true'. The only child, arranged to the left, is labeled Counter with a state bubble labeled 'count' and value 0. All of the left child is highlighted in yellow, indicating it was added.

প্রাথমিক স্টেট

Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'false'. The state bubble is highlighted in yellow, indicating that it has changed. The left child is replaced with a yellow 'poof' image indicating that it has been deleted and there is a new child on the right, highlighted in yellow indicating that it was added. The new child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0.
Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'false'. The state bubble is highlighted in yellow, indicating that it has changed. The left child is replaced with a yellow 'poof' image indicating that it has been deleted and there is a new child on the right, highlighted in yellow indicating that it was added. The new child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0.

“next” বাটনটি চাপালে

Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'true'. The state bubble is highlighted in yellow, indicating that it has changed. There is a new child on the left, highlighted in yellow indicating that it was added. The new child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is replaced with a yellow 'poof' image indicating that it has been deleted.
Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'true'. The state bubble is highlighted in yellow, indicating that it has changed. There is a new child on the left, highlighted in yellow indicating that it was added. The new child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is replaced with a yellow 'poof' image indicating that it has been deleted.

আবার “next” বাটনটি চাপালে

প্রতিটি Counter এর স্টেট প্রতিবার DOM থেকে অপসারিত করা হলে ধ্বংস হয়ে যায়। এটি হচ্ছে কেন তারা প্রতিবার আপনি বাটনটি চাপার সময় রিসেট হয়।

এই সমাধানটি সুবিধাজনক যখন আপনার একই স্থানে কয়েকটি স্বতন্ত্র কম্পোনেন্ট রেন্ডার করা হয়। এই উদাহরণে আপনার কেবল দুটি কম্পোনেন্ট আছে, তাই JSX এ উভয়কে পৃথক ভাবে রেন্ডার করা একটি ঝামেলা নয়।

Option 2: Resetting state with a key

আরও একটি, ভাবে কম্পোনেন্টের স্টেট রিসেট করার একটি সাধারণ উপায় আছে।

আপনি সম্ভবত দেখেছেন key গুলি যখন তালিকা রেন্ডার করা হয়। কীগুলি শুধুমাত্র তালিকার জন্য নয়! আপনি কীগুলি ব্যবহার করে রিয়েক্টকে যেকোনো কম্পোনেন্টগুলি পৃথক করতে ব্যবহার করতে পারেন। ডিফল্টভাবে, রিয়েক্ট অর্ডার ব্যবহার করে প্যারেন্টের মধ্যে (“প্রথম কাউন্টার”, “দ্বিতীয় কাউন্টার”) কম্পোনেন্টগুলি পৃথক করতে। কিন্তু কীগুলি আপনাকে বলতে দিয়ে দিতে পারে যে এটি শুধুমাত্র একটি প্রথম কাউন্টার নয়, অথবা দ্বিতীয় কাউন্টার নয়, বরং একটি নির্দিষ্ট কাউন্টার - উদাহরণস্বরূপ, টেইলরের কাউন্টার। এই ভাবে, রিয়েক্ট জানতে পারবে যে যেখানে এটি প্রদর্শিত হয়, সেখানে টেইলরের কাউন্টার!

এই উদাহরণে, প্রদর্শিত হলেও দুটি <Counter /> সমস্ত অবস্থানে স্টেট ভাগ করে না:

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter key="Taylor" person="Taylor" />
      ) : (
        <Counter key="Sarah" person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

টেইলর এবং সারাহের মধ্যে স্টেট সংরক্ষিত থাকলেও স্থিতিশীল রাখা হয়না। এটি কারণ আপনি তাদের জন্য ভিন্ন key দিয়েছেন:

{isPlayerA ? (
<Counter key="Taylor" person="Taylor" />
) : (
<Counter key="Sarah" person="Sarah" />
)}

একটি key নির্দিষ্ট করা রিয়েক্টকে বলে দেয় যে এটি স্থানের একটি অংশ হিসাবে ব্যবহার করবে, প্যারেন্টের অর্ডারের পরিবর্তে। এটি কারণেই, যখনই আপনি তাদের একই স্থানে JSX এ রেন্ডার করেন, রিয়েক্ট তাদের দুটি পৃথক কাউন্টার হিসাবে ধরে নেয়। সুতরাং, তারা কখনই স্টেট ভাগ করবে না। যেখানেই একটি কাউন্টার পর্যালোচনা করা হয়, তার স্টেট তৈরি হয়। যেকোনো সময় যখন এটি অপসারিত হয়, তখন এর স্টেট ধ্বংস হয়ে যায়। তাদের মধ্যে টগলিং করা হলে বারবার তাদের স্টেট রিসেট হয়।

খেয়াল করুন

মনে রাখবেন যে কীগুলি গ্লোবালী অনন্য নয়। তারা কেবল প্যারেন্টের মধ্যে অবস্থান নির্দিষ্ট করে

একটি কী ব্যবহার করে ফর্ম রিসেট করা

ফর্ম সম্পর্কে কথা বলার সময় কী ব্যবহার করে স্টেট রিসেট করা অত্যন্ত উপযুক্ত।

এই চ্যাট অ্যাপে, <Chat> কম্পোনেন্টটিতে টেক্সট ইনপুট স্টেট রয়েছে।:

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat contact={to} />
    </div>
  )
}

const contacts = [
  { id: 0, name: 'Taylor', email: 'taylor@mail.com' },
  { id: 1, name: 'Alice', email: 'alice@mail.com' },
  { id: 2, name: 'Bob', email: 'bob@mail.com' }
];

ইনপুটে কিছু লিখে প্রেস করুন এবং তারপর “আলিস” বা “বব” এ প্রেস করুন যাতে আপনি একটি পৃথক প্রাপক প্রেস করতে পারেন। আপনি দেখবেন যে ইনপুট স্টেট সংরক্ষিত থাকে কারণ <Chat> একই অবস্থানে ট্রি রেন্ডার করা হয়।

অনেক অ্যাপস এই অবস্থানটি কার্যকর হতে পারে, কিন্তু চ্যাট অ্যাপে এটি কার্যকর হবে না! একটি অকার্যকর ক্লিকের ফলে ব্যবহারকারীকে একটি ভুল ব্যক্তির কাছে যেটি তারা ইতিমধ্যে লিখেছেন পাঠিয়ে দেওয়া উচিত নয়। এটি ঠিক করতে, একটি key যুক্ত করুন:

<Chat key={to.id} contact={to} />

এটি নিশ্চিত করে যে যখন আপনি একটি পৃথক প্রাপক নির্বাচন করেন, তখন Chat কম্পোনেন্টটি পুনরায় সৃষ্টি হবে, নিচের ট্রির কোনো স্থিতির সাথে স্ক্র্যাচ থেকে নতুন স্থানে। রিয়েক্ট এটিও পুনরায় DOM উপাদানগুলি সৃষ্টি করবে, ব্যবহার করার পরিবর্তে।

এখন প্রাপক পরিবর্তন করলে সর্বদা টেক্সট ফিল্ড পরিষ্কার হয়:

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.id} contact={to} />
    </div>
  )
}

const contacts = [
  { id: 0, name: 'Taylor', email: 'taylor@mail.com' },
  { id: 1, name: 'Alice', email: 'alice@mail.com' },
  { id: 2, name: 'Bob', email: 'bob@mail.com' }
];

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

অপসারিত কম্পোনেন্টগুলির জন্য স্টেট সংরক্ষণ করা

একটি প্রাসঙ্গিক চ্যাট অ্যাপে, আপনি সম্ভবত পূর্ববর্তী প্রাপকটি আবার নির্বাচন করলে ইনপুট স্টেট পুনরুদ্ধার করতে চান। একটি দৃষ্টিগোচর নয় কোম্পোনেন্টের জন্য স্টেটকে “সজীব” রাখতে কিছু উপায় আছে:

  • আপনি বর্তমান প্রাপ্যের পরিবর্তে সমস্ত চ্যাট সংখ্যায় রেন্ডার করতে পারেন, তবে সকল অন্যান্য চ্যাটগুলি CSS দ্বারা লুকিয়ে রাখবেন। চ্যাটগুলি ট্রি থেকে অপসারিত হবেনা, তাই তাদের স্থানীয় স্টেটটি সংরক্ষিত থাকবে। এই সমাধানটি সাধারণ UI এর জন্য বিশেষভাবে ভালো কাজ করে। কিন্তু যদি লুকিয়ে রাখা ট্রি বড় হয় এবং বহুগুণ DOM নোড ধারণ করে, তবে এটি খুব নিউনতম হতে পারে।
  • আপনি স্টেটটি উপরে উঠিয়ে তুলতে পারেন এবং প্রতিটি প্রাপকের জন্য মাতৃক কম্পোনেন্টে মুল্য রাখতে পারেন। এই ভাবে, যখন শিশু কম্পোনেন্টগুলি অপসারিত হয়, তখন এটি ব্যাপার নেই, কারণ প্যারেন্টটি গুরুত্বপূর্ণ তথ্য রাখে। এটি সবচেয়ে সাধারণ সমাধান।
  • আপনি আপাতত রিয়েক্ট স্টেটের পাশাপাশি অতিরিক্ত একটি উৎস ব্যবহার করতে পারেন। উদাহরণস্বরূপ, ব্যবহারকারী যদি দুর্ঘটনাপূর্বক পৃষ্ঠাটি বন্ধ করে তবে মেসেজ ড্রাফটটি ধ্বংস না হয়। এটি সম্পাদন করতে, আপনি Chat কম্পোনেন্টটির স্টেটকে localStorage থেকে পড়ে তুলতে পারেন এবং ড্রাফটগুলি সেখানেই সংরক্ষণ করতে পারেন।

আপনি যে কোনটি ব্যবহার করুন, অলিসের সাথে একটি চ্যাট অ্যাপ সংজ্ঞায়িতভাবে বিচক্ষণ একটি চ্যাট থেকে ভিন্ন, তাই বর্তমান প্রাপকের উপর ভিত্তি করে <Chat> ট্রির জন্য একটি key দেওয়া যায়।

পুনরালোচনা

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

Challenge 1 of 5:
অপসারিত ইনপুট পাঠ্য ঠিক করুন

এই উদাহরণে আপনি বাটন চাপার সময় একটি বার্তা প্রদর্শন করেন। তবে, বাটন চাপার সাথে সাথেই ইনপুট ভুলভাবে পুনরায় স্থাপন হয়ে যায়। কেন এটা ঘটছে? এটিকে ঠিক করুন যাতে বাটন চাপার মাধ্যমে ইনপুট পাঠ্যটি পুনরায় স্থাপন না হয়।

import { useState } from 'react';

export default function App() {
  const [showHint, setShowHint] = useState(false);
  if (showHint) {
    return (
      <div>
        <p><i>Hint: Your favorite city?</i></p>
        <Form />
        <button onClick={() => {
          setShowHint(false);
        }}>Hide hint</button>
      </div>
    );
  }
  return (
    <div>
      <Form />
      <button onClick={() => {
        setShowHint(true);
      }}>Show hint</button>
    </div>
  );
}

function Form() {
  const [text, setText] = useState('');
  return (
    <textarea
      value={text}
      onChange={e => setText(e.target.value)}
    />
  );
}