আগের পর্বে 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:
- উপরের
useFetchনিজে বানাও, দুইটা ভিন্ন component-এ দুইটা ভিন্ন API endpoint দিয়ে টেস্ট করো। - উপরের
useDebounceনিজে বানাও, একটা সার্চ বক্সেuseFetch-এর সাথে মিলিয়ে ব্যবহার করো। - উপরের
useFetchWithCacheনিজে বানাও, ইন্টারনেট বন্ধ করে (DevTools-এর Offline মোড) দেখো ক্যাশড ডেটা দেখায় কিনা। - একটা নতুন Custom Hook
usePrevious(value)বানাও, যেটাuseRefদিয়ে একটা ভ্যালুর "আগের" মান মনে রাখবে (হিন্ট: পর্ব ১৭-এর useRef রিভিউ করো)।
পরের পর্বে যা থাকছে
পরের পর্বে আমরা শিখব React.memo ও Re-render নিয়ন্ত্রণ — কেন component অকারণে re-render হয়, memo দিয়ে কীভাবে সেটা আটকানো যায়, আর React DevTools দিয়ে কীভাবে প্রোফাইল করে সমস্যা খুঁজে বের করা যায়।