React এর হাতেখড়ি (পর্ব ১৩) — useEffect দিয়ে Fetch API কল করা শিখুন

useEffect দিয়ে Fetch API কল করা

আগের পর্বে আমরা useEffect-এর বেসিক শিখেছিলাম। এই পর্বে আমরা সেটার সবচেয়ে বাস্তব ব্যবহার দেখব — API থেকে ডেটা ফেচ করা। এতে আগের পর্বগুলোর প্রায় সব কনসেপ্ট (useState, Conditional Rendering, List Rendering, Error Handling) একসাথে কাজে লাগবে।

তিনটা State লাগবে

API কল করার সময় সাধারণত তিনটা স্টেট ট্র্যাক করতে হয় — Conditional Rendering পর্বে যেই "তিন-অবস্থার প্যাটার্ন" দেখেছিলাম, সেটাই এখানে বাস্তবে প্রয়োগ হবে।

function BookList() {
  const [books, setBooks] = React.useState([]); // ডেটা
  const [isLoading, setIsLoading] = React.useState(true); // লোড হচ্ছে কিনা
  const [error, setError] = React.useState(null); // এরর হয়েছে কিনা

  // ...
}

useEffect-এর ভেতরে fetch করা

একটা গুরুত্বপূর্ণ নিয়ম — useEffect-এর ফাংশন সরাসরি async হতে পারে না, তাই ভেতরে একটা আলাদা async ফাংশন বানিয়ে সেটা কল করতে হয়।

function BookList() {
  const [books, setBooks] = React.useState([]);
  const [isLoading, setIsLoading] = React.useState(true);
  const [error, setError] = React.useState(null);

  useEffect(() => {
    async function fetchBooks() {
      try {
        const response = await fetch("https://api.example.com/books");
        if (!response.ok) {
          throw new Error("বই লোড করা যায়নি");
        }
        const data = await response.json();
        setBooks(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setIsLoading(false); // সফল হোক বা ব্যর্থ, লোডিং শেষ
      }
    }

    fetchBooks();
  }, []); // শুধু একবার, component mount হওয়ার সময়

  // ... রেন্ডার লজিক নিচে
}

এখানে Advanced JS পর্ব ১০-এর try/catch/finally সরাসরি কাজে লাগছে — finally নিশ্চিত করছে isLoading সবসময় false হবে, ফলাফল যাই হোক।

তিন-অবস্থার Conditional Rendering

এখন পর্ব ৭-এ শেখা Conditional Rendering দিয়ে তিনটা অবস্থা আলাদাভাবে দেখানো:

function BookList() {
  // ... আগের state আর useEffect ...

  if (isLoading) {
    return <p>লোড হচ্ছে...</p>;
  }

  if (error) {
    return <p>এরর: {error}</p>;
  }

  return (
    <ul>
      {books.map((book) => (
        <li key={book.id}>{book.title}</li>
      ))}
    </ul>
  );
}

এখানে পর্ব ৮-এর List Rendering (map() + key) আর পর্ব ৭-এর early return প্যাটার্ন একসাথে ব্যবহার হচ্ছে — এটাই বাস্তব প্রজেক্টে ডেটা ফেচিং-এর সবচেয়ে সাধারণ কাঠামো।

লাইভ ডেমো — আসল API থেকে ডেটা ফেচ করা

নিচে সত্যিকারের একটা পাবলিক API (JSONPlaceholder) থেকে ডেটা আনা হচ্ছে — লোডিং, এরর, আর সফল অবস্থা তিনটাই বাস্তবে দেখো।

এটা সত্যিই jsonplaceholder.typicode.com থেকে লাইভ ডেটা আনছে — পেজ রিফ্রেশ করলে useEffect আবার চলবে।

Race Condition — একটা লুকানো সমস্যা

যদি dependency পাল্টানোর সাথে সাথে বারবার fetch হয় (যেমন সার্চ বক্সে টাইপ করার সাথে সাথে), একটা সমস্যা হতে পারে — পুরনো রিকোয়েস্ট দেরিতে ফিরে এসে নতুন ডেটা "ওভাররাইট" করে ফেলতে পারে। এটাকে বলে race condition

function SearchResults({ query }) {
  const [results, setResults] = React.useState([]);

  useEffect(() => {
    let isCancelled = false; // এই effect-এর জন্য একটা "পতাকা"

    async function search() {
      const response = await fetch(`/api/search?q=${query}`);
      const data = await response.json();

      if (!isCancelled) {
        // শুধু তখনই আপডেট করো যদি এই effect "বাতিল" না হয়ে থাকে
        setResults(data);
      }
    }

    search();

    // cleanup — dependency পাল্টালে বা component চলে গেলে এটা চলে
    return () => {
      isCancelled = true; // পুরনো রিকোয়েস্টের ফলাফল উপেক্ষা করার নির্দেশ
    };
  }, [query]); // query পাল্টালেই আবার সার্চ হবে

  return (
    <ul>
      {results.map((item) => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

এখানে আগের পর্বের cleanup function ব্যবহার হচ্ছে ভিন্নভাবে — শুধু event listener সরানোর জন্য না, বরং পুরনো, "দেরি করে আসা" ফলাফল উপেক্ষা করার জন্য।

Dependency অনুযায়ী নতুন করে fetch করা

যখন কোনো prop বা state পাল্টালে নতুন ডেটা আনতে হবে (যেমন একটা নির্দিষ্ট বইয়ের ID পাল্টালে), সেই ভ্যারিয়েবল dependency array-তে দিতে হবে।

function BookDetail({ bookId }) {
  const [book, setBook] = React.useState(null);
  const [isLoading, setIsLoading] = React.useState(true);

  useEffect(() => {
    setIsLoading(true); // নতুন bookId হলে আবার লোডিং দেখাও

    async function fetchBook() {
      const response = await fetch(`/api/books/${bookId}`);
      const data = await response.json();
      setBook(data);
      setIsLoading(false);
    }

    fetchBook();
  }, [bookId]); // bookId পাল্টালেই আবার fetch হবে

  if (isLoading) return <p>লোড হচ্ছে...</p>;

  return <h1>{book.title}</h1>;
}

একটা সম্পূর্ণ উদাহরণ — সার্চেবল বই তালিকা

চলো List Rendering পর্বের সার্চ ফিচারটা এবার আসল API দিয়ে বানাই — user নাম দিয়ে সার্চ, প্রতিটা অক্ষরে নতুন fetch।

function UserSearch() {
  const [query, setQuery] = React.useState("");
  const [users, setUsers] = React.useState([]);
  const [isLoading, setIsLoading] = React.useState(false);

  useEffect(() => {
    if (query.trim() === "") {
      setUsers([]);
      return;
    }

    let isCancelled = false;
    setIsLoading(true);

    async function searchUsers() {
      try {
        const response = await fetch(`https://api.example.com/users?search=${query}`);
        const data = await response.json();
        if (!isCancelled) {
          setUsers(data);
        }
      } finally {
        if (!isCancelled) {
          setIsLoading(false);
        }
      }
    }

    searchUsers();

    return () => {
      isCancelled = true;
    };
  }, [query]);

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="ইউজার খুঁজো" />
      {isLoading && <p>খোঁজা হচ্ছে...</p>}
      <ul>
        {users.map((user) => <li key={user.id}>{user.name}</li>)}
      </ul>
    </div>
  );
}

এই component-এ Controlled Input (পর্ব ৯), useEffect dependency (পর্ব ১২), race condition handling, আর List Rendering (পর্ব ৮) — সবকিছু একসাথে মিলিত হয়েছে।

নিজে চেষ্টা করো

Exercise:

  1. উপরের FetchDemo নিজে বানাও, _limit=4-এর বদলে _limit=10 দিয়ে টেস্ট করো।
  2. একটা URL ইচ্ছাকৃতভাবে ভুল দিয়ে দেখো error state ঠিকমতো কাজ করছে কিনা।
  3. উপরের BookDetail-এর মতো একটা component বানাও যেটা https://jsonplaceholder.typicode.com/posts/{id} থেকে একটা নির্দিষ্ট পোস্ট দেখাবে, একটা বাটন দিয়ে id পাল্টানো যাবে।
  4. race condition-এর isCancelled প্যাটার্নটা নিজের ভাষায় ব্যাখ্যা করে লিখে রাখো — এটা কেন দরকার।

পরের পর্বে যা থাকছে

পরের পর্বে আমরা শিখব Lifting State Up — যখন দুইটা sibling component-কে একে অপরের সাথে "কথা বলাতে" হয়, তখন কীভাবে callback function ব্যবহার করে চাইল্ড থেকে প্যারেন্টে ডেটা পাঠানো যায়।

একটি মন্তব্য পোস্ট করুন

নবীনতর পূর্বতন

যোগাযোগ ফর্ম