Advance React শিখুন বাংলায় (পর্ব ৫) — Custom Hook গভীরভাবে শিখুন

Custom Hook গভীরভাবে

আগের পর্বে useCallback শিখেছিলাম। এই পর্বে আমরা ফিরছি Custom Hook-এ — React এর হাতেখড়ি পর্ব ১৭-এ বেসিক দেখেছিলাম, এবার আরও গভীরে যাব: একাধিক built-in Hook মেলানো, আর একটা বাস্তব, পুনর্ব্যবহারযোগ্য data-fetching hook বানানো, যা এই সিরিজের এতদিনের প্রায় সব শেখা একসাথে ব্যবহার করবে।

সমস্যাটা কী? — Fetch লজিক বারবার লেখা

React এর হাতেখড়ি পর্ব ১৩-তে আমরা useEffect দিয়ে fetch করা শিখেছিলাম — loading, error, data তিনটা state, try/catch/finally, cleanup। যদি একটা প্রজেক্টে ৫টা ভিন্ন component-এ ৫টা ভিন্ন API কল করতে হয়, এই একই কাঠামো পাঁচবার কপি-পেস্ট করা লাগবে।

// ❌ একই প্যাটার্ন বারবার — শুধু URL আলাদা
function BookList() {
  const [data, setData] = React.useState([]);
  const [isLoading, setIsLoading] = React.useState(true);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    async function fetchData() {
      try {
        const res = await fetch("/api/books");
        setData(await res.json());
      } catch (e) {
        setError(e.message);
      } finally {
        setIsLoading(false);
      }
    }
    fetchData();
  }, []);
  // ...
}

function UserList() {
  // প্রায় হুবহু একই কোড, শুধু "/api/users"
}

সমাধান — useFetch Custom Hook

চলো এই পুরো প্যাটার্নটা একটা reusable Hook-এ গুটিয়ে ফেলি।

// useFetch.js
function useFetch(url) {
  const [data, setData] = React.useState(null);
  const [isLoading, setIsLoading] = React.useState(true);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    let isCancelled = false; // পর্ব ১৩-এর race condition সমাধান
    setIsLoading(true);
    setError(null);

    async function fetchData() {
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error("ডেটা লোড করা যায়নি");
        const json = await res.json();
        if (!isCancelled) setData(json);
      } catch (err) {
        if (!isCancelled) setError(err.message);
      } finally {
        if (!isCancelled) setIsLoading(false);
      }
    }

    fetchData();

    return () => {
      isCancelled = true;
    };
  }, [url]); // url পাল্টালেই আবার fetch হবে

  return { data, isLoading, error };
}

এখন ব্যবহার অনেক সহজ:

function BookList() {
  const { data: books, isLoading, error } = useFetch("/api/books");
  // Destructuring-এর সাথে renaming (Advanced JS পর্ব ৪) — data-কে books নাম দেওয়া

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

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

function UserList() {
  const { data: users, isLoading, error } = useFetch("/api/users");
  // একই Hook, ভিন্ন URL — সম্পূর্ণ স্বাধীন state
}

দুই লাইনেই এখন loading, error, আর data — তিনটাই পাওয়া যাচ্ছে। পুরো fetch লজিক একটা মাত্র জায়গায় (useFetch.js) আছে — একবার বাগ ঠিক করলে সব জায়গায় সেই ফিক্স পৌঁছে যাবে।

লাইভ ডেমো — একই useFetch, দুইটা ভিন্ন URL

নিচে useFetch দুইবার ব্যবহার হচ্ছে — একবার posts-এর জন্য, একবার users-এর জন্য।

একাধিক Hook মেলানো — useFetch + useLocalStorage

Custom Hook-এর ভেতরে অন্য Custom Hook-ও ব্যবহার করা যায় — পর্ব ১৭-এর useLocalStorage আর এই পর্বের useFetch মিলিয়ে একটা তৃতীয়, আরও শক্তিশালী Hook বানানো যায় — যেমন ডেটা fetch করে ক্যাশ করে রাখা।

function useLocalStorage(key, defaultValue) {
  const [value, setValue] = React.useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : defaultValue;
  });
  React.useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  return [value, setValue];
}

// দুটো Hook মিলিয়ে একটা তৃতীয় Hook
function useFetchWithCache(url, cacheKey) {
  const [cachedData, setCachedData] = useLocalStorage(cacheKey, null);
  const { data, isLoading, error } = useFetch(url);

  React.useEffect(() => {
    if (data) {
      setCachedData(data); // নতুন ডেটা এলে ক্যাশ আপডেট
    }
  }, [data]);

  // ইন্টারনেট ধীর হলেও পুরনো ক্যাশড ডেটা সাথে সাথে দেখানো যায়
  return { data: data || cachedData, isLoading, error };
}

এখানে useFetchWithCache নিজে দুটো Custom Hook (useLocalStorage, useFetch) একসাথে ব্যবহার করে একটা নতুন আচরণ তৈরি করছে — এভাবে ছোট ছোট Hook একসাথে জুড়ে জটিল ফিচার বানানো React-এর একটা শক্তিশালী প্যাটার্ন।

আরেকটা প্যাটার্ন — useDebounce

Advanced JS পর্ব ১-এর debounce ফাংশন মনে আছে? সেটাকেই একটা Custom Hook বানানো যায়, যেটা সার্চ ইনপুটের সাথে useFetch-এর সাথে মিলিয়ে ব্যবহার করা যায়।

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = React.useState(value);

  React.useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timer); // আগের টাইমার বাতিল, নতুন ভ্যালু এলে
  }, [value, delay]);

  return debouncedValue;
}
function SearchBooks() {
  const [searchInput, setSearchInput] = React.useState("");
  const debouncedSearch = useDebounce(searchInput, 500); // ৫০০ms পর ভ্যালু আপডেট হবে
  const { data, isLoading } = useFetch(`/api/books?q=${debouncedSearch}`);

  return (
    <div>
      <input value={searchInput} onChange={(e) => setSearchInput(e.target.value)} />
      {/* প্রতিটা কী-প্রেসে fetch হয় না, টাইপিং থামার ৫০০ms পরে হয় */}
    </div>
  );
}

এখানে searchInput সাথে সাথেই পাল্টায় (ইনপুট রেসপন্সিভ থাকে), কিন্তু debouncedSearch (আর তাই useFetch-এর URL) শুধু টাইপিং থামার পরই আপডেট হয় — এতে API কল অনেক কমে যায়।

লাইভ ডেমো — useDebounce + useFetch একসাথে

দ্রুত অনেকগুলো অক্ষর টাইপ করেও দেখো — fetch কাউন্ট প্রতিটা কী-প্রেসে বাড়ে না, শুধু থামলে বাড়ে।

Custom Hook লেখার সময় ভালো অভ্যাস

  • একটা Hook, একটা দায়িত্বuseFetch শুধু fetch করবে, useDebounce শুধু debounce করবে — একটা Hook-এ সব গুঁজে না দিয়ে ছোট, কম্পোজেবল রাখা
  • অবজেক্ট রিটার্ন করা যখন একাধিক নামযুক্ত মান দরকার ({ data, isLoading, error }), অ্যারে রিটার্ন করা যখন useState-এর মতো একটা মান+সেটার প্যাটার্ন হয়
  • ডিফল্ট মান দেওয়া — যেমন useDebounce(value, delay = 500), ব্যবহারকারীর কাজ কমাতে

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

Exercise:

  1. উপরের useFetch নিজে বানাও, দুইটা ভিন্ন component-এ দুইটা ভিন্ন API endpoint দিয়ে টেস্ট করো।
  2. উপরের useDebounce নিজে বানাও, একটা সার্চ বক্সে useFetch-এর সাথে মিলিয়ে ব্যবহার করো।
  3. উপরের useFetchWithCache নিজে বানাও, ইন্টারনেট বন্ধ করে (DevTools-এর Offline মোড) দেখো ক্যাশড ডেটা দেখায় কিনা।
  4. একটা নতুন Custom Hook usePrevious(value) বানাও, যেটা useRef দিয়ে একটা ভ্যালুর "আগের" মান মনে রাখবে (হিন্ট: পর্ব ১৭-এর useRef রিভিউ করো)।

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

পরের পর্বে আমরা শিখব React.memo ও Re-render নিয়ন্ত্রণ — কেন component অকারণে re-render হয়, memo দিয়ে কীভাবে সেটা আটকানো যায়, আর React DevTools দিয়ে কীভাবে প্রোফাইল করে সমস্যা খুঁজে বের করা যায়।

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

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

যোগাযোগ ফর্ম