Effects দিয়ে Synchronizing

কিছু কম্পোনেন্ট কে বাইরের সিস্টেমের সাথে সিংক্রোনাইজ করতে হতে পারে। উদাহরণস্বরূপ, আপনি নন-রিয়েক্ট কম্পোনেন্টকে রিয়েক্ট state এর উপর নির্ভর করে নিয়ন্ত্রণ করতে চান, একটি সার্ভার সংযোগ স্থাপন করতে চান, বা যখন একটি কম্পোনেন্ট স্ক্রিনে দেখা যায় তখন একটি বিশ্লেষণ লগ পাঠাতে চান। Effects আপনাকে রেন্ডার এর পর কিছু কোড রান করার সুযোগ দেয় যাতে আপনি আপনার কম্পোনেন্টটি রিয়েক্টের বাইরে কোন সিস্টেম এর সঙ্গে সিংক্রোনাইজ করতে পারেন।

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

  • Effects কী
  • কীভাবে Effect গুলো events থেকে আলাদা
  • কীভাবে আপনার কম্পোনেন্টে Effect ডিক্লার করবেন
  • কীভাবে অকারণে কোন Effect রি-রানিং এড়াবেন
  • কেন ডেভেলপমেন্টের সময় Effects দুইবার রান হয় এবং সেগুল কীভাবে ঠিক করবেন

Effects কী এবং কীভাবে সেগুলো events থেকে আলাদা?

Effects সম্পর্কে শুরুর আগে, আপনার রিয়েক্ট কম্পোনেন্টের ভেতরের দুই প্রকার লজিকের সাথে পরিচিয় থাকতে হবে:

  • Rendering code (যা UI এর বর্ণনা অধ্যায়ে পরিচয় দেওয়া হয়েছে ) আপনার কম্পোনেন্টের টপ লেভেলে থাকে। এটি সেখানে থাকে, যেখানে আপনি props এবং state নেন, তাদের পরিবর্তন করেন, এবং আপনি যে JSX দেখতে চান তা রিটার্ন করেন। Rendering code অবশ্যই পিওর হতে হবে একটি গণিত সূত্র মতো, যে সূত্রটি শুধু ফলাফিল হিসাব করে, কিন্তু অন্য কিছু করে না।

  • Event handlers (যা Adding Interactivity অধ্যায়ে পরিচয় দেওয়া হয়েছে) আপনার কম্পোনেন্টের ভিতরে একটি নেস্টেড ফাংশন যা কেবল সেগুলো গণনা করার পরিবর্তে অন্য কিছু করে। এটি যে কাজগুলো করতে পারে সেগুলো হতে পারে একটি ইনপুট ফিল্ড আপডেট করা, একটি পণ্য কিনতে HTTP POST request দেওয়া, অথবা ব্যবহারকারীকে অন্য একটি স্ক্রিনে navigate করা। Event handler এ “side effects” থাকে (এগুলো program এর অবস্থা পরিবর্তন করে) যা নির্দিষ্ট ব্যবহারকারীর ক্রিয়া (উদাহরণস্বরূপ button click অথবা typing)।

কখনও কখনও এটা যথেষ্ট নয়। একটি ChatRoom কম্পোনেন্ট চিন্তা করুন যখনই স্ক্রিনে দৃশ্যমান হয় তখন অবশ্যই চ্যাট সার্ভারের সাথে সংযোগ করতে হয়। সার্ভারে সংযোগ স্থাপন pure calculation নয় (এটি একটি side effect) তাই এটি রেন্ডার এর সময় সম্পন্ন হয় না। যাইহোক, ক্লিক ইভেন্ট এর মত কোন নির্দিষ্ট ইভেন্ট নাই যা ChatRoom ডিসপ্লে করায়।

Effect গুলো নির্দিষ্ট ইভেন্টের মাধ্যমে নয়, বরং স্বয়ংক্রিয় রেন্ডারিং দ্বারা সৃষ্ট side effect গুলো নির্দিষ্ট করতে দেয়। চ্যাটে message পাঠানো একটি event কারণ এটি সরাসরি একজন ব্যবহারকারীর দ্বারা একটি নির্দিষ্ট বাটনে ক্লিক করার মাধ্যমে ঘটে। তবু, সার্ভারের সাথে সংযোগ স্থাপন একটি Effect কারণ এটা উপস্থিত কোম্পোনেন্টের প্রদর্শনের কোন ইন্টারেকশনের কারণে হয় না। Effect গুলো স্ক্রিন আপডেটের পরে একটি commit এর শেষে চালানো হয়। কিছু external system (যেমন network অথবা একটি third-party library) এর সাথে React component গুলো synchronize করার জন্য এটি একটি ভাল সময় ।

খেয়াল করুন

এখানে এবং পরে এই পাঠটিতে, বড় হাতের “Effect” উপরের React-specific সংজ্ঞা বোঝায়, অর্থাৎ রেন্ডারিংয়ের ফলে সৃষ্ট side effect। বিস্তৃত এই প্রোগ্রামিং concept টি বুঝাতে, আমরা এটিকে “side effect” বলব।

আপনার কোন Effect প্রয়োজন নাও হতে পারে

অপ্রয়োজনে আপনার component এ Effects অ্যাড করবেন না। মনে রাখবেন যে Effect গুলো সাধারণত আপনার React কোডের “step out” করতে এবং কিছু বাহ্যিক সিস্টামের সাথে synchronize করতে ব্যবহৃত হয়। এর মধ্যে রয়েছে browser APIs, third-party widgets, network, এবং আরও অনেক কিছু। যদি আপনার Effect টি কেবল অন্য state এর উপর ভিত্তি করে কিছু state কে সামঞ্জস্য করে, তবে আপনার কোন Effect প্রয়োজন নাও হতে পারে।

কিভাবে একটি Effect লিখবেন

একটি Effect লিখতে, এই তিনটি ধাপ অনুসরণ করুনঃ

  1. Effect ডিক্লার By default, প্রত্যেক বার রেন্ডারের সময় Effect রান হবে।

  2. Effect এর dependenci গুলো specify করুন বেশিরভাগ Effects প্রত্যেকবার রেন্ডার হওয়ার পরে re-run হওয়ার থেকে যখন প্রয়োজন তখন re-run হওয়া উচিৎ। উদাহরণস্বরূপ, একটি fade-in animation কেবল তখনি টিগার করা উচিৎ যখন কোন একটি component দৃশ্যমান হয়। কোন chat room এর সাথে সংযোগ স্থাপন এবং বিচ্ছিন্ন তখনই ঘটে যখন component টি দৃশ্যমান এবং অদৃশ্যমান হয়ে যায় বা যখন chat room টি পরিবর্তন হয়। কীভাবে dependencies specifying এর মাধ্যমে এটি কন্ট্রোল করবেন তা শিখবেন।

  3. প্রয়োজনে cleanup অ্যাড করুন কিছু Effects কিভাবে থামানো হবে, আন্ডু হবে বা এগুলো যা করছে তা clean up করতে হবে তা specify করে দিতে হয়। উধাহরণস্বরূপ, “connect” এর জন্য প্রয়োজন “disconnect”, “subscribe” এর জন্য “unsubscribe”, and “fetch” এর জন্য প্রয়োজন হয়ত “cancel” অথবা “ignore”। আপনি একটি cleanup function রিটার্ন করে কীভাবে এটি করবেন তা শিখবেন। আসুন, এবার প্রতিটি ধাপ বিস্তারিত দেখি।

ধাপ ১: একটি Effect ডিক্লার

আপনার component এ কোন Effect ডিক্লার করতে, useEffect হুক React থেকে import করুন:

import { useEffect } from 'react';

এরপরে, এটিকে আপনার component এর top level এ call করুন এবং Effects এর মধ্যে কিছু code রাখুন।

function MyComponent() {
useEffect(() => {
// *প্রতিবার* রেন্ডারে এখানের code রান হবে
});
return <div />;
}

প্রতিবার যখন component রেন্ডার করবে, React স্কিন আপডেট করবে এবং এর পরে useEffect এর ভিতরের কোড রান করবে। অর্থাৎ, useEffect এক টুকরা কোড রান হতে ” বিলম্ব করায় ” যতক্ষণ না রেন্ডারটি স্কিনে reflected হয়।

চলুন দেখা যাক কিভাবে আপনি Effect ব্যবহার করে একটি external system এর সাথে synchronize করবেন। একটি <VideoPlayer> React component এর কথা চিন্তা করুন। এটি কন্ট্রল করতে ভাল হবে যদি এটিতে একটি isPlaying প্রপস পাঠানো হয় যে এটি চালু আছে অথবা বন্ধ:

<VideoPlayer isPlaying={isPlaying} />;

আপনার কাস্টম VideoPlayer component টি ব্রাউজারের built-in <video> tag রেন্ডার করে:

function VideoPlayer({ src, isPlaying }) {
// TODO: do something with isPlaying
return <video src={src} />;
}

তবে, browser এর <video> tag এ isPlaying প্রপ্স নাই। এটি নিয়ন্ত্রণের একমাত্র উপয় হলো DOM element টিতে ম্যানুয়ালি play() এবং pause() call করা। আপনাকে isPlaying প্রপ্স এর value টি synchronize করতে হবে, যা play() এবং pause()কে কল করে video টি বর্তমানে বাজানো উচিৎ কিনা তা নির্দেশ করে।

আমাদের প্রথমে <video> DOM node এর একটি ref পেতে হবে

রেন্ডারিং এর সময় আপনি play() অথবা pause() কল করার চেষ্টা করতে পারেন, তবে এটি সঠিক নয়:

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  if (isPlaying) {
    ref.current.play();  // Calling these while rendering isn't allowed.
  } else {
    ref.current.pause(); // Also, this crashes.
  }

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

এই কোডটি সঠিক না হওয়ার কারণ হলো এটি রেন্ডারিং এর সময় DOM node এর সাথে কিছু একটা করার চেষ্টা করে। React এ, রেন্ডারিং JSX এর pure calculation হওয়া উচিৎ এবং DOM কে modify করে এমন কোন side effects থাকা উচিৎ নয়।

উপরন্ত, যখন VideoPlayer কে প্রথমবারের জন্য call করা হয়, এটির DOM তখন exist করে না! play() বা pause() করার জন্য এখানে কোন DOM node নাই, কারণ React জানে না কি DOM তৈরি হবে যতক্ষণ না আপনি JSX রিটার্ন করেন।

এখানে সমাধানটি হলো রেন্ডারিং calculation এর বাইরে সরানোর জন্য useEffect এর দ্বারা side effect টি wrap করে রাখা:

import { useEffect, useRef } from 'react';

function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);

useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
});

return <video ref={ref} src={src} loop playsInline />;
}

DOM update কে একটি Effect দিয়ে wrap করার মাধ্যমে, আপনি প্রথমে React কে screen টি আপডেট করতে দিন। এরপরে আপনার Effect রান হবে।

যখন আপনার VideoPlayer component টি রেন্ডার করে (হয় প্রথমবার বা যদি এটি পুনরায় রেন্ডার করে), কয়েকটি জিনিস ঘটবে। প্রথমে, React স্কিন আপডেট করবে, <video> tag টি সঠিক প্রপস সহ DOM এ আছে কিনা তা নিশ্চিত করবে । তারপরে React আপনার Effect চালাবে। অবশেষে, আপনার Effect টি isPlaying এর মানের উপর depend করে play() বা pause() কল করবে।

Play/Pause একাধিকবার চাপুন এবং দেখুন video player কিভাবে isPlaying এর value তে synchronize থাকে:

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  });

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

এই উদাহরণে, আপনি যে “external system” React state এর সাথে synchronize করেছেন তা হলো ব্রাউজার মিডিয়া API। আপনি legacy non-React code (যেমন jQuery plugins) থেকে declarative React component এ wrap করতে অনুরূপ পদ্ধতি ব্যবহার করেতে পারেন।

মনে রাখবেন যে কোন ভিডিও প্লেয়ার কন্ট্রল করা প্রাক্টিকালি আরও জটিল। play() কল fail হতে পারে, user built-in ব্রাউজার control গুলো ব্যবহার করে play বা pause করতে পারে, এবং আরও অনেক কিছু। এই উদাহরণটি খুবই সহজ এবং অসম্পূর্ণ।

সতর্কতা

By default, Effect গুলো প্রত্যেক রেন্ডারের পরে run হয়। এ কারণেই এ জাতীয় কোড infinite loop তৈরি করে:

const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
});

রেন্ডারিং এর ফলস্বরূপ Effect চলে। state সেট করা রেন্ডারিং টি টিগার করে। একটি Effect সিঙ্গে সঙ্গে state এ সেট করা যেমন একটি পাওয়ার আউটলেটকে তার নিজেতেই প্লাগ করা। Effect run হয়, এটি state সেট করে, যা একটি re-render তৈরি করে, যার ফলে Effect টি run হয়, এটি আবার state টি সেট করে, এটি অন্য একটি re-render তৈরি করে, আর এভাবেই চলতে থাকে।

Effect গুলো সাধারণত আপনার component গুলোকে একটি external system এর সাথে synchronize করে। যদি কোন external system না থাকে এবং আপনি কেবল অন্য state এর উপর ভিত্তি করে কিছু state এডজাস্ট করতে চান, আপনার কোন Effect প্রয়োজন নাও হতে পারে।

ধাপ ২: Effect এর dependency গুলো নির্দিষ্ট করুন

By default, Effect গুলো প্রত্যেক রেন্ডারের পরে run হয়। অনেক সময়, এটি আপনি চান না:

  • কখনো কখনো, এটি slow কাজ করে। একটি external system এর সাথে Synchroniz করা সর্বদা তাতক্ষণিক হয় না, সুতরং আপনি এটি প্রয়োজন না হলে এটি এড়িয়ে যেতে চাইতে পারেন। উদাহরণস্বরূপ, আপনি প্রতি keystoke এ চ্যাট সার্ভারের সাথে পুনরায় সংযোগ স্থাপন করতে চান না।
  • কখনো কখনো, এটি ভুল। উদাহরণস্বরূপ, আপনি প্রতিটি keystroke এ কোন component ফেড-ইন animation ট্রিগার করতে চান না। component টি প্রথমবারের মত appear হলে animation টি কেবল একবার play হওয়া উচিৎ।

সমস্যাটি প্রদর্শনের করতে, এখানে কয়েকটি console.log কল এবং একটি টেক্সট ইনপুট সহ পূর্ববর্তী উদাহরণটি যেটি parent component এর স্টেটকে update করে । খেয়াল করুন কিভাবে typing এর ফলে Effect টি re-run হয়:

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      console.log('Calling video.play()');
      ref.current.play();
    } else {
      console.log('Calling video.pause()');
      ref.current.pause();
    }
  });

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

আপনি useEffect এর দ্বিতীয় আর্গুমেন্ট হিসাবে dependency এর একটি array specify করে React কে অপ্রয়োজনীয়ভাবে Effect টি re-running এড়িয়ে যেতে বলতে পারেন। উপরের উদাহরণের ১৪ লাইনে একটি খালি [] array যুক্ত করে শুরু করুন:

useEffect(() => {
// ...
}, []);

আপনি একটি error দেখতে পাবেন যে React Hook useEffect has a missing dependency: 'isPlaying':

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      console.log('Calling video.play()');
      ref.current.play();
    } else {
      console.log('Calling video.pause()');
      ref.current.pause();
    }
  }, []); // This causes an error

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

সমস্যাটি হলো আপনার Effect এর মধ্যের কোড কি করবে তা সিদ্ধান্ত নেওয়ার জন্য isPlaying প্রপ্সের উপর নির্ভর করে, কিন্তু এই dependency টি স্পষ্টভাবে declare করা হয়নি। এই সমস্যাটির সমাধান করতে, dependency array তে isPlaying যুক্ত করুন:

useEffect(() => {
if (isPlaying) { // It's used here...
// ...
} else {
// ...
}
}, [isPlaying]); // ...so it must be declared here!

এখন সকল dependency গুলো declar করা হয়ে গেছে, সুতারং কোন error নাই। [isPlaying] কে dependency array তে রাখার মানে হলো React কে বলা যে যদি isPlaying এর মান আগের রেন্ডারে যেমন ছিল তেমন থাকে তবে re-running স্কিপ করতে। এই পরিবর্তনের কারণে, ইনপুট ফিল্ডটিতে টাইপ করালেও Effect টি re-run হয় না, কিন্তু Play/Pause বাটনে press করলে হয়:

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      console.log('Calling video.play()');
      ref.current.play();
    } else {
      console.log('Calling video.pause()');
      ref.current.pause();
    }
  }, [isPlaying]);

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

dependency array তে একাধিক dependency থাকতে পারে। যদি সবগুলো dependency এর value গুলো previous render এর মতই থাকে কেবল তখনই React Effect টি re-runn করবে না। React dependency value গুলোকে তুলনা করতে Object.is comparison ব্যবহার করে। বিস্তারির জানতে useEffect reference দেখুন।

লক্ষ্য করুন যে আপনি আপনার dependency গুলো “choose” করতে পারছেন না। আপনি যে dependecy গুলো specify করেছেন তা যদি আপনি Effect এর মধ্যে যে কোড রেখেছেন তার উপর base করে React এর expectation এর সাথে না মিলে তাহলে আপনি একটি lint error পাবেন। এটি আপনার কোডে অনেক bug খুজে পাতে সাহায্য করে । যদি আপনি কছু কোড re-run করতে না চান, Effect কোড edit করুন যাতে ঐ dependencyর “প্রয়োজন” না হয়।

সতর্কতা

dependency array ছাড়া এবং একটি empty [] dependency array সহ এদের behavior আলাদা হয়ে থাকে:

useEffect(() => {
// This runs after every render
});

useEffect(() => {
// This runs only on mount (when the component appears)
}, []);

useEffect(() => {
// This runs on mount *and also* if either a or b have changed since the last render
}, [a, b]);

আমরা পরবর্তি step এ “mount” এর মানে কী তা ভালোভাবে দেখবো।

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

dependency array থেকে কেন ref বাদ দেওয়া হয়েছিল?

এই Effect টিতে ref এবং isPlaying উভয়ই ব্যবহার হচ্ছে, কিন্তু কেবল isPlaying কে dependency হিসাবে ডিক্লার করা হয়েছে:

function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying]);

এর কারণ হল ref object এর একটি stable identity রয়েছে: React গ্যারান্টি দেয় যে প্রতি রেন্ডারে একই useRef কল থেকে সর্বদা একই object পাবেন। এটি কখনো পরিবর্তন হয় না, সুতারং এটি নিজেই Effect টি re-run হওয়ার কারণ হতে পারেনা। অতএব, এটি বিবেচ্য বিষয় নয় যে আপনি এটি include করছেন কি করেন নাই। এটি Includ করাও ঠিক আছে:

function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying, ref]);

useState দ্বারা রিটার্ন করা set function গুলোরও stable identity রয়েছে, তাই আপনি প্রায়ই দেখতে পাবেন তাদের dependencie থেকে বাদ দেওয়া হয়েছে। যদি lint আপনাকে error ছাড়াই dependency বাদ দিতে দেয়, তবে এটি করা নিরাপদ।

always-stable dependency বাদ দেওয়া তখনই কাজ করে যখন linter “দেখতে” পারে যা object টি stable। উদাহরণস্বরূপ, যদি কোন parent component থেকে ref pass করা হয়, আপনাকে একটি dependency array specify করতে হবে। যাইহোক, এটি ভাল কারণ আপনি জানতে পারবেন না যে parent component সবসময় একই রেফ পাস করে কিনা, অথবা শর্তসাপেক্ষে বেশ কয়েকটি রেফের একটি পাস করে কিনা। সুতারং আপনার Effect নির্ভর করবে কোন ref pass করা হয়েছে তার উপর।

ধাপ ৩: প্রয়োজনে cleanup যোগ করুন

একটি ভিন্ন উদাহরণ বিবেচনা করুন। আপনি একটি ChatRoom component লিখেছেন যা এটি প্রদর্শিত হওয়ার সময় chat server এর সাথে সংযোগ স্থাপন করা দরকার। আপনাকে একটি createConnection() API দেওয়া হয়েছে যেটি connect() এবং disconnect() method এর একটি object রিটার্ন করে। user এর কাছে প্রদর্শিত হওয়ার সময় আপনি কিভাবে component টিকে সংযুক্ত রাখবেন?

Effect logic লিখে শুরু করুন:

useEffect(() => {
const connection = createConnection();
connection.connect();
});

প্রত্যেকবার re-render এর পরে chat এর সাথে সংযোগ স্থাপন করা ধীর হবে, সুতারং আপনি dependency array যুক্ত করুন:

useEffect(() => {
const connection = createConnection();
connection.connect();
}, []);

Effect এর ভিতরের কোড কোন props or state ব্যবহার করে না, সুতারং আপনার dependency array টি [] (empty)। এটি React কে শুধুমাত্র তখনই এই কোডটি চালাতে বলে যখন component টি “মাউন্ট” হয়, অর্থাৎ, প্রথমবারের জন্য স্কিনে উপস্থিত হয়।

আসুন code টি রান করার চেষ্টা করি:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
  }, []);
  return <h1>Welcome to the chat!</h1>;
}

এই Effect টি কেবল মাউন্ট হওয়ার সময় চলে, সুতারং আপনি প্রত্যাশা করতে পারেন console একবার "✅ Connecting..." প্রিন্ট হবে। তবে, আপনি যদি console চেক করেন, দেখবেন "✅ Connecting..." দুই বার প্রিন্ট হয়েছে। কেন এমন হচ্ছে?

কল্পনা করুন যে ChatRoom এর component টি অনেক গুলো ভিন্ন ভিন্ন স্কিন সহ একটি বৃহিত্তর app এর একটি অংশ। ব্যবহারকারী তাদের journey শুরু করে ChatRoom পেইজ দিয়ে। component টি মাউন্ট করে এবং connection.connect() কে কল করে। তারপরে কল্পনা করুন যে ব্যবহারকারী অন্য স্কিনে নেভিগেট করেছে —উদাহরণস্বরূপ, Settings পেইজে। এখন ChatRoom এর component আনমাউন্ট। অবশেষে, ব্যবহারকারী Back এ ক্লিক করে এবং ChatRoom টি আবার মাউন্ট করে। এটি একটি second connection স্থাপন করবে—তবে প্রথম connection টি কখনই বিচ্ছিন্ন হয়নি! ব্যবহারকারী অ্যাপ জুড়ে নেভিগেট করার সাথে সাথে সংযোগগুলি pulling হতে থাকবে।

এই ধরনের বাগগুলি ব্যাপক ম্যানুয়াল পরীক্ষা ছাড়া সহজই মিস হয়ে যায়। আপনাকে দ্রুত সেগুলি সনাক্ত করতে সহায়তা করার জন্য, React development এ প্রতিটি component কে তার প্রাথমিক মাউন্টের পরপরই পুনরায় মাউন্ট করে।

"✅ Connecting..." দু’বার log হচ্ছে দেখা আপনাকে আসল সমস্যাটি লক্ষ্য করতে সাহায্য করে: যখন component টি আনমিউট হয় আপনার কোড সংযোগটি বন্ধ করে না।

সমস্যাটি সমাধান করেতে, আপনার Effect থেকে একটি cleanup function return করুন:

useEffect(() => {
const connection = createConnection();
connection.connect();
return () => {
connection.disconnect();
};
}, []);

Effect পুনরায় run হওয়ার আগে প্রতিবার আপনার cleanup function কে কল করবে, এবং শেষ সময় যখন component টি আনমিউট করে (রিমুভ করা হয়)। আসুন দেখা যাক যখন cleanup function টি implemente করা হয় তখন কি ঘটে:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, []);
  return <h1>Welcome to the chat!</h1>;
}

এখন আপনি development এ তিনটি console log পাবেন:

  1. "✅ Connecting..."
  2. "❌ Disconnected."
  3. "✅ Connecting..."

এটি development এর সঠিক behavior। আপনার component রিমাউন্টিং করে, React যাচাই করে যে নেভিগেট করে সামনে গিয়ে এবং পিছনে back করলে আপনার কোড ব্রেক করবে না। সংযোগ বিচ্ছিন্ন এবং তারপর আবার সংযোগ স্থাপন করলে ঠিক কি হওয়া উচিৎ! যখন আপনি cleanup টি ভালোভাবে implement করেন, Effect টি একবার run করা vs এটি চালাতে থাকা, এটি cleaning করা এবং পুনরায় run করার মধ্যে কোন ব্যবহারকারীর দৃশ্যমান পার্থক্য থাকা উচিত নয়। এখানে একটি অতিরিক্ত কানেক্ট/ডিসকানেক্ট কল পেয়ার আছে কারণ React ডেভেলপমেন্টে থাকা বাগগুলির জন্য আপনার কোড পরীক্ষা করছে। এটি স্বাভাবিক—এটিকে দুরে সরিয়ে দেওয়ার চেষ্টা করবেন না!

production এ, আপনি কেবল একবার "✅ Connecting..." প্রিন্ট হতে দেখতে পাবেন। component গুলো রিমাউন্টং কেবল development এর ক্ষেত্রে ঘটে যা আপনাকে এমন Effect গুলো খুঁজে পেতে সাহায্য করে যা ক্লিনাপের প্রয়োজন। আপনি development behavior থেকে বেরিয়ে বেরিয়ে আসার জন্য Strict Mode অফ করতে পারেন, তবে আমরা এটি চালিয়ে যাওয়ার পরামর্শ দেই। এটি আপনাকে উপরের মত অনেক গুলো বাগ খুঁজে পেতে সাহায্য করবে।

How to handle the Effect firing twice in development?

React ইচ্ছাকৃতভাবে আপনার কম্পোনেন্ট গুলোকে ডেভেলপমেন্টে রিমাউন্ট করে যাতে শেষ উদাহরণের মতো বাগ খুঁজে পাওয়া যায়। প্রশ্ন ঠিক নয় “কীভাবে একটি Effect একবার চালাতে হয়”, তবে “কিভাবে আমার Effect টি ঠিক করব যাতে এটি পুনরায় মাউন্ট করার পরে কাজ করে”।

সাধারণত, উত্তর হল ক্লিনআপ ফাংশন implement করা। ক্লিনআপ ফাংশনটির বন্ধ করা উচিৎ বা Effect যা কিছু করতেছল তা পূর্বাবস্থায় ফিরিয়ে আনা উচিৎ। rule of thumb হল যে ব্যবহারকারী একবার Effect run হওয়া (production এ) এবং একটি setup → cleanup → setup সিকোয়েন্সের (যেমন আপনি development এ দেখতে পাবেন) মধ্যে পার্থক্য করতে সক্ষম হবে না।

আপনি যে Effect গুলো লিখবেন তার বেশিরভাগই নীচের সাধারণ প্যাটার্নগুলির মধ্যে একটিতে ফিট হবে৷

non-React widget গুলো controll করা

কখনো কখনো আপনাকে UI widget অ্যাড করতে হবে যা React দিয়ে লেখা হয়নি। উদাহরণস্বরূপ, আপনি আপনার পেইজে একটি ম্যাপ component অ্যাড করেছেন। এটিতে একটি setZoomLevel() method রয়েছে, এবং আপনি আপনার React কোডে zoomLevel স্টেট variable এর সাথে zoom level সিঙ্ক রাখতে চান। আপনার Effect এটির মত দেখতে হবে:

useEffect(() => {
const map = mapRef.current;
map.setZoomLevel(zoomLevel);
}, [zoomLevel]);

মনে রাখবেন যে এই ক্ষেত্রে কোন cleanup এর প্রয়োজন নেই। development এ, React দু’বার Effect কল করে, কিন্তু এটি কোন সমস্যা নয় কারণ একই value সহ setZoomLevel কে দু’বার কল করলে কিছুই হবে না। এটি কিছুটা স্লো হতে পারে, তবে ব্যাপার না কারণ এটি production এ অযথা রিমাউন্ট করবে না।

কিছু API আপনাকে পরপর দুবার কল করার allow নাও দিতে পারে। উদাহরণস্বরূপ, বিল্ট-ইন <dialog> ইলিমেন্টটি showModal ম্যাথোড থ্রো করে যদি আপনি এটিকে দুবার কল করেন। cleanup function টি ইমপ্লিমেন্ট করুন এবং ডায়লগটি close করুন:

useEffect(() => {
const dialog = dialogRef.current;
dialog.showModal();
return () => dialog.close();
}, []);

development এ, আপনার Effect showModal() কল করবে, তারপর immediately close() কল করবে, এবং এরপর আবার showModal() কল করবে। showModal() কে একবার কল করার মতই user-visible behavior এটির, যেমনটি আপনি production এ দেখতে পাবেন।

ইভেন্ট subscribing

যদি আপনার Effect কোন কিছু সাবস্ক্রাইব করে, তবে ক্লিনআপ ফাংশনটির তা আনসাবস্ক্রাইব করা উচিৎ:

useEffect(() => {
function handleScroll(e) {
console.log(window.scrollX, window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);

development এ, আপনার Effect addEventListener() কে কল করবে, তারপর immediately removeEventListener() কে, এবং তারপরে আবার addEventListener() কে একই handler দিয়ে কল করবে। তাই এক সময়ে শুধুমাত্র একটি subscription এক্টিভ থাকবে। addEventListener() কে একবার কল করার মতই user-visible behavior এটির, যেমনটি আপনি production এ দেখতে পাবেন।

animation টিগার করা

যদি আপনার Effect টি কিছু animate করে, তবে আপনার ক্লিনাপ function টির উচিৎ initial value দিয়ে animation টি reset করা:

useEffect(() => {
const node = ref.current;
node.style.opacity = 1; // Trigger the animation
return () => {
node.style.opacity = 0; // Reset to the initial value
};
}, []);

development এ, opacity সেট করা হবে 1, এরপরে 0, এবং তারপরে আবার 1 । এটি সরাসরি 1 এ সেট করার মতই user-visible behavior হওয়া উচিৎ, যা production এ ঘটবে। আপনি যদি tweening এর জন্য support সহ একটি third-party অ্যানিমেশন লাইব্রেরি ব্যবহার করেন, তবে আপনার ক্লিনআপ ফাংশনটি টাইমলাইনটিকে তার initial state পুনরায় সেট করা উচিৎ।

ডাটা Fetch করা

যদি আপনার Effect টি কিছু fetch করে, তবে আপনার ক্লিনাপ function টির উচিৎ হয়ত fetch বাতিল করা অথবা এর ফলাফল ignore করা:

useEffect(() => {
let ignore = false;

async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}

startFetching();

return () => {
ignore = true;
};
}, [userId]);

নেটওয়ার্ক রিকুয়েস্ট যা ইতিমধ্যে ঘটে দিয়েছেন, তা আপনি “বাতিল” করতে পারবেন না, তবে আপনার ক্লিন-আপ ফাংশনটির এটি নিশ্চিত করতে হবে যে, যে fetch গুলো আর প্রাসঙ্গিক নয় সেগুলো আপনার অ্যাপ্লিকেশনে প্রভাব ফেলবে না। যদি userId 'Alice' থেকে 'Bob' পরিবর্তন করে, তবে ক্লিন-আপ নিশ্চিত করেবে যে 'Alice' রেসপন্সটি 'Bob' এর পরেও যদি আসে তাহলেও সেটি আপনার অ্যাপ্লিকেশনে প্রভাবিত করবে না।

development এ, আপনি Network tab এ দুটি fetch দেখতে পাবেন। এতে কোন সমস্যা নাই। উপরের পদ্ধতিতে, প্রথম ইফেক্টটি তাৎক্ষণিকভাবে ক্লিন-আপ হবে তাই তার ignore ভেরিয়েবলের কপি true হবে। তাই, যদিও অতিরিক্ত একটি রিকুয়েস্ট আছে, সুতরাং অতিরিক্ত অনুরোধ থাকা সত্ত্বেও, এটি state কে প্রভাবিত করবে না if (!ignore) চেক কে ধন্যবাদ।

production এ, কেবল একটি request থাকবে। যদি development এর দ্বিতীয় request টি আপনাকে বিরক্ত করে, তবে সর্বোত্তম পদ্ধতি হল এমন একটি সমাধান ব্যবহার করা যা request গুলিকে ডিডপ্লিকেট করেবে এবং component গুলো তাদের response গুলো ক্যাশ করেবে:

function TodoList() {
const todos = useSomeDataLibrary(`/api/user/${userId}/todos`);
// ...

এটি শুধুমাত্র ডেভেলপমেন্ট experience ই improve করবে না, বরং আপনার অ্যাপ্লিকেশনকে দ্রুত অনুভব করতে সাহায্য করবে। উদাহরণস্বরূপ, ব্যবহারকারী যদি ব্যাক বোতাম চাপে, তাকে আবার কিছু ডেটা লোড করতে অপেক্ষা করতে হয় না কারণ সেটি ক্যাশ করা থাকবে। আপনি এমন একটি ক্যাশ নিজেই তৈরি করতে পারেন অথবা Effect এ ম্যানুয়াল ফেচিংয়ের জন্য অনেকগুলো alternative ব্যবহার করতে পারেন।

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

Effect ডেটা ফেচিংয়ের জন্য ভাল Alternatives কী?

Effect এ fetch কল লেখা ডেটা ফেচিংর জন্য একটি জনপ্রিয় উপায়, বিশেষভাবে সম্পূর্ণ ক্লায়েন্ট-সাইড অ্যাপসগুলোতে। তবে, এটি একটি অনেকটাই ম্যানুয়াল পদ্ধতি এবং এটির কিছু উল্লেখযোগ্য downside রয়েছে:

  • এফেক্টস সার্ভারে চলতে পারে না। যার মানে, initial সার্ভার একটি লোডিং স্টেট সহ HTML রেন্ডার করবে কোনো ডেটা ছাড়াই। ক্লায়েন্ট কম্পিউটারকে সমস্ত জাভাস্ক্রিপ্ট ডাউনলোড করতে হবে এবং আপনার অ্যাপটি রেন্ডার করতে হবে শুধুমাত্র এটি discover করতে যে এটির এখন ডেটা লোড করতে হবে। এটি খুব একটা efficient না।

  • Effects এ সরাসরি ডেটা ফেচিং “নেটওয়ার্ক ওয়াটারফল” তৈরি করতে সাহায্য করে। আপনি parent কম্পোনেন্টটি রেন্ডার করেন, এটি কিছু ডেটা ফেচ করে, চাইল্ড কম্পোনেন্টগুলো রেন্ডার হয়, এবং তারপরে তারা তাদের ডেটা ফেচ করতে শুরু করে। যদি নেটওয়ার্ক খুব ফাস্ট না হয়, এটি সব ডেটা পারালেলভাবে ফেচ হইয়ার তুলনায় অনেকটাই ধীর।

  • মূলত Effect এ সরাসরি ডেটা ফেচ করার মানে এই যে, আপনি ডেটা প্রিলোড বা ক্যাশ করতে পারবেন না। উদাহরণস্বরূপ, যদি কোম্পোনেন্টটি আনমাউন্ট হয় এবং পুনরায় মাউন্ট হয়, এটিকে পুনরায় ডাটা ফেচ করতে হবে।

  • এটা খুব একটা ইর্গোনমিক নয়। ফেচ কল লেখার সময়, যদি রেস কন্ডিশন এর মতো বাগে ছাফার হতে না হয়, তার জন্য কিছু বয়লারপ্লেট কোড থাকে।

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

  • আপনি যদি একটি framework ব্যবহার করেন, তার built-in ডেটা ফেটচিং প্রক্রিয়া ব্যবহার করুন। আধুনিক রিয়্যাক্ট ফ্রেমওয়ার্কগুলির মধ্যে integrated ডেটা ফেটচিং প্রক্রিয়া রয়েছে যা কার্যকর এবং উপরের সমস্যা গুলো মুক্ত।
  • অন্যথায়, একটি ক্লায়েন্ট-সাইড ক্যাশ ইউজ করুন বা বিল্ড করুন। জনপ্রিয় ওপেন সোর্স সমাধানের মধ্যে React Query, useSWR, এবং React Router 6.4+. রয়েছে। আপনি একটি নিজস্ব সমাধানো তৈরি করতে পারেন এই ক্ষেত্রে আপনি Effect গুলো আন্ডার দ্যা হুডে ব্যবহার করতে পারেন, তবে request ডিডুপ্লিকেট করাতে, response ক্যাশ করতে, এবং নেটওয়ার্ক ওয়াটারফল এড়াতে লজ্যিক add করুন। (ডাটা প্রিলোডিং করে বা ডাটা requirement গুলো রাউটে hoisting করে)।

যদি এই পদক্ষেপগুলোর মধ্যে কোনটিই আপনার জন্য প্রযোজ্য না হয়, তবে সরাসরি ইফেক্টে ডেটা ফেটচিং চালিয়ে যেতে পারেন।”

Sending analytics

Consider this code that sends an analytics event on the page visit:

useEffect(() => {
logVisit(url); // Sends a POST request
}, [url]);

In development, logVisit will be called twice for every URL, so you might be tempted to try to fix that. We recommend keeping this code as is. Like with earlier examples, there is no user-visible behavior difference between running it once and running it twice. From a practical point of view, logVisit should not do anything in development because you don’t want the logs from the development machines to skew the production metrics. Your component remounts every time you save its file, so it logs extra visits in development anyway.

In production, there will be no duplicate visit logs.

To debug the analytics events you’re sending, you can deploy your app to a staging environment (which runs in production mode) or temporarily opt out of Strict Mode and its development-only remounting checks. You may also send analytics from the route change event handlers instead of Effects. For more precise analytics, intersection observers can help track which components are in the viewport and how long they remain visible.

Not an Effect: Initializing the application

Some logic should only run once when the application starts. You can put it outside your components:

if (typeof window !== 'undefined') { // Check if we're running in the browser.
checkAuthToken();
loadDataFromLocalStorage();
}

function App() {
// ...
}

This guarantees that such logic only runs once after the browser loads the page.

Not an Effect: Buying a product

Sometimes, even if you write a cleanup function, there’s no way to prevent user-visible consequences of running the Effect twice. For example, maybe your Effect sends a POST request like buying a product:

useEffect(() => {
// 🔴 Wrong: This Effect fires twice in development, exposing a problem in the code.
fetch('/api/buy', { method: 'POST' });
}, []);

You wouldn’t want to buy the product twice. However, this is also why you shouldn’t put this logic in an Effect. What if the user goes to another page and then presses Back? Your Effect would run again. You don’t want to buy the product when the user visits a page; you want to buy it when the user clicks the Buy button.

Buying is not caused by rendering; it’s caused by a specific interaction. It should run only when the user presses the button. Delete the Effect and move your /api/buy request into the Buy button event handler:

function handleClick() {
// ✅ Buying is an event because it is caused by a particular interaction.
fetch('/api/buy', { method: 'POST' });
}

This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs. From a user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, then pressing Back to view the page again. React verifies that your components abide by this principle by remounting them once in development.

Putting it all together

This playground can help you “get a feel” for how Effects work in practice.

This example uses setTimeout to schedule a console log with the input text to appear three seconds after the Effect runs. The cleanup function cancels the pending timeout. Start by pressing “Mount the component”:

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    function onTimeout() {
      console.log('⏰ ' + text);
    }

    console.log('🔵 Schedule "' + text + '" log');
    const timeoutId = setTimeout(onTimeout, 3000);

    return () => {
      console.log('🟡 Cancel "' + text + '" log');
      clearTimeout(timeoutId);
    };
  }, [text]);

  return (
    <>
      <label>
        What to log:{' '}
        <input
          value={text}
          onChange={e => setText(e.target.value)}
        />
      </label>
      <h1>{text}</h1>
    </>
  );
}

export default function App() {
  const [show, setShow] = useState(false);
  return (
    <>
      <button onClick={() => setShow(!show)}>
        {show ? 'Unmount' : 'Mount'} the component
      </button>
      {show && <hr />}
      {show && <Playground />}
    </>
  );
}

You will see three logs at first: Schedule "a" log, Cancel "a" log, and Schedule "a" log again. Three second later there will also be a log saying a. As you learned earlier, the extra schedule/cancel pair is because React remounts the component once in development to verify that you’ve implemented cleanup well.

Now edit the input to say abc. If you do it fast enough, you’ll see Schedule "ab" log immediately followed by Cancel "ab" log and Schedule "abc" log. React always cleans up the previous render’s Effect before the next render’s Effect. This is why even if you type into the input fast, there is at most one timeout scheduled at a time. Edit the input a few times and watch the console to get a feel for how Effects get cleaned up.

Type something into the input and then immediately press “Unmount the component”. Notice how unmounting cleans up the last render’s Effect. Here, it clears the last timeout before it has a chance to fire.

Finally, edit the component above and comment out the cleanup function so that the timeouts don’t get cancelled. Try typing abcde fast. What do you expect to happen in three seconds? Will console.log(text) inside the timeout print the latest text and produce five abcde logs? Give it a try to check your intuition!

Three seconds later, you should see a sequence of logs (a, ab, abc, abcd, and abcde) rather than five abcde logs. Each Effect “captures” the text value from its corresponding render. It doesn’t matter that the text state changed: an Effect from the render with text = 'ab' will always see 'ab'. In other words, Effects from each render are isolated from each other. If you’re curious how this works, you can read about closures.

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

Each render has its own Effects

You can think of useEffect as “attaching” a piece of behavior to the render output. Consider this Effect:

export default function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);

return <h1>Welcome to {roomId}!</h1>;
}

Let’s see what exactly happens as the user navigates around the app.

Initial render

The user visits <ChatRoom roomId="general" />. Let’s mentally substitute roomId with 'general':

// JSX for the first render (roomId = "general")
return <h1>Welcome to general!</h1>;

The Effect is also a part of the rendering output. The first render’s Effect becomes:

// Effect for the first render (roomId = "general")
() => {
const connection = createConnection('general');
connection.connect();
return () => connection.disconnect();
},
// Dependencies for the first render (roomId = "general")
['general']

React runs this Effect, which connects to the 'general' chat room.

Re-render with same dependencies

Let’s say <ChatRoom roomId="general" /> re-renders. The JSX output is the same:

// JSX for the second render (roomId = "general")
return <h1>Welcome to general!</h1>;

React sees that the rendering output has not changed, so it doesn’t update the DOM.

The Effect from the second render looks like this:

// Effect for the second render (roomId = "general")
() => {
const connection = createConnection('general');
connection.connect();
return () => connection.disconnect();
},
// Dependencies for the second render (roomId = "general")
['general']

React compares ['general'] from the second render with ['general'] from the first render. Because all dependencies are the same, React ignores the Effect from the second render. It never gets called.

Re-render with different dependencies

Then, the user visits <ChatRoom roomId="travel" />. This time, the component returns different JSX:

// JSX for the third render (roomId = "travel")
return <h1>Welcome to travel!</h1>;

React updates the DOM to change "Welcome to general" into "Welcome to travel".

The Effect from the third render looks like this:

// Effect for the third render (roomId = "travel")
() => {
const connection = createConnection('travel');
connection.connect();
return () => connection.disconnect();
},
// Dependencies for the third render (roomId = "travel")
['travel']

React compares ['travel'] from the third render with ['general'] from the second render. One dependency is different: Object.is('travel', 'general') is false. The Effect can’t be skipped.

Before React can apply the Effect from the third render, it needs to clean up the last Effect that did run. The second render’s Effect was skipped, so React needs to clean up the first render’s Effect. If you scroll up to the first render, you’ll see that its cleanup calls disconnect() on the connection that was created with createConnection('general'). This disconnects the app from the 'general' chat room.

After that, React runs the third render’s Effect. It connects to the 'travel' chat room.

Unmount

Finally, let’s say the user navigates away, and the ChatRoom component unmounts. React runs the last Effect’s cleanup function. The last Effect was from the third render. The third render’s cleanup destroys the createConnection('travel') connection. So the app disconnects from the 'travel' room.

Development-only behaviors

When Strict Mode is on, React remounts every component once after mount (state and DOM are preserved). This helps you find Effects that need cleanup and exposes bugs like race conditions early. Additionally, React will remount the Effects whenever you save a file in development. Both of these behaviors are development-only.

পুনরালোচনা

  • Unlike events, Effects are caused by rendering itself rather than a particular interaction.
  • Effects let you synchronize a component with some external system (third-party API, network, etc).
  • By default, Effects run after every render (including the initial one).
  • React will skip the Effect if all of its dependencies have the same values as during the last render.
  • You can’t “choose” your dependencies. They are determined by the code inside the Effect.
  • Empty dependency array ([]) corresponds to the component “mounting”, i.e. being added to the screen.
  • In Strict Mode, React mounts components twice (in development only!) to stress-test your Effects.
  • If your Effect breaks because of remounting, you need to implement a cleanup function.
  • React will call your cleanup function before the Effect runs next time, and during the unmount.

Challenge 1 of 4:
Focus a field on mount

In this example, the form renders a <MyInput /> component.

Use the input’s focus() method to make MyInput automatically focus when it appears on the screen. There is already a commented out implementation, but it doesn’t quite work. Figure out why it doesn’t work, and fix it. (If you’re familiar with the autoFocus attribute, pretend that it does not exist: we are reimplementing the same functionality from scratch.)

import { useEffect, useRef } from 'react';

export default function MyInput({ value, onChange }) {
  const ref = useRef(null);

  // TODO: This doesn't quite work. Fix it.
  // ref.current.focus()    

  return (
    <input
      ref={ref}
      value={value}
      onChange={onChange}
    />
  );
}

To verify that your solution works, press “Show form” and verify that the input receives focus (becomes highlighted and the cursor is placed inside). Press “Hide form” and “Show form” again. Verify the input is highlighted again.

MyInput should only focus on mount rather than after every render. To verify that the behavior is right, press “Show form” and then repeatedly press the “Make it uppercase” checkbox. Clicking the checkbox should not focus the input above it.