সমস্যা এবং সমাধান: যখন বারবার সাবমিট বোতামে ক্লিক করা হয়, তখন পূর্বের ফেচ অনুরোধ বিচ্ছিন্ন হয় না কেন? 🤔
অনেক সময় এমন হয় যে আমরা কোনো ওয়েবসাইটে কোনো ফর্ম সাবমিট করার জন্য সাবমিট বোতামে একাধিকবার ক্লিক করে ফেলি। যদি সার্ভার থেকে সাড়া আসতে দেরি হয়, তাহলে মনে হতে পারে প্রথমবার ক্লিকটি কাজ করেনি। কিন্তু এর ফলে অনেক সময় অপ্রত্যাশিত সমস্যা দেখা দিতে পারে, যেমন একই অনুরোধ একাধিকবার পাঠানো বা পূর্বের অনুরোধ শেষ হওয়ার আগেই নতুন অনুরোধ শুরু হওয়া।
এই ব্লগ পোস্টে আমরা আলোচনা করব কেন এমনটা ঘটে এবং কীভাবে এই সমস্যার সমাধান করা যায়। আমরা একটি React কোড উদাহরণ দেখব এবং চিহ্নিত করব কোথায় সমস্যাটি হচ্ছে এবং কীভাবে তা সমাধান করা যায়।
সমস্যার উৎস 🕵️♀️
উপরে দেওয়া React কোডটিতে handleSubmit
ফাংশনের মধ্যে ছবি জেনারেট করার জন্য একটি লুপ চালানো হচ্ছে (৯ বার)। প্রতিটি ইটারেশনে একটি নতুন ফেচ অনুরোধ শুরু হয়। সমস্যাটা হল, যদি ব্যবহারকারী সাবমিট বোতামে একাধিকবার ক্লিক করে, তাহলে handleSubmit
ফাংশনটি ততবার কল হবে এবং সমান্তরালভাবে একাধিকবার ৯টি করে ফেচ অনুরোধ শুরু হয়ে যাবে।
এখানে মূল সমস্যাগুলো হল:
- পূর্বের অনুরোধ ট্র্যাক করা হচ্ছে না: যখন দ্বিতীয়বার সাবমিট করা হচ্ছে, তখন প্রথমবার শুরু হওয়া ফেচ অনুরোধগুলো সম্পর্কে কোনো তথ্য রাখা হচ্ছে না।
- নতুন করে শুরু: প্রতিবার সাবমিট করার সময় নতুন করে ৯টি ফেচ অনুরোধ শুরু হচ্ছে, পূর্বেরগুলো বাতিল করা হচ্ছে না।
এর ফলে সার্ভারে অতিরিক্ত চাপ পড়তে পারে এবং ব্যবহারকারী অপ্রত্যাশিত ফলাফল দেখতে পারে।
সমাধানের উপায় 🛠️
এই সমস্যার সমাধানের জন্য আমরা একটি ফ্ল্যাগ ব্যবহার করতে পারি যা নির্দেশ করবে যে বর্তমানে কোনো ফেচ অনুরোধ চলছে কিনা। যদি কোনো অনুরোধ চলমান থাকে, তাহলে সাবমিট বোতামে ক্লিক করা হলেও নতুন করে কোনো অনুরোধ শুরু হবে না।
নিচে সংশোধিত কোডটি দেওয়া হল:
অতিরিক্ত বিবেচনা 💡
- ব্যবহারকারীর অভিজ্ঞতা: যদিও এই সমাধানটি একাধিকবার সাবমিট করার সমস্যা সমাধান করে, তবে ব্যবহারকারীকে জানানো উচিত যে তাদের প্রথম অনুরোধটি প্রক্রিয়াধীন রয়েছে। এর জন্য আপনি একটি লোডিং ইন্ডিকেটর বা বার্তা দেখাতে পারেন যখন
isFetching
এর মানtrue
থাকে। - বাতিল করার সুবিধা: আপনি ব্যবহারকারীকে চলমান ফেচ অনুরোধ বাতিল করার সুযোগ দিতে পারেন যদি তারা মনে করে অনেকক্ষণ অপেক্ষা করতে হচ্ছে। এর জন্য
AbortController
ব্যবহার করা যেতে পারে।
আশা করি এই ব্লগ পোস্টটি আপনার প্রশ্নের উত্তর দিতে পেরেছে এবং আপনি বুঝতে পেরেছেন কেন বারবার সাবমিট করলে পূর্বের অনুরোধ বিচ্ছিন্ন হয় না এবং কীভাবে এই সমস্যার সমাধান করা যায়। Happy coding! 😊
Part-2
অবশ্যই! ব্যবহারকারী যখন ফেচ অনুরোধ বাতিল করে, তখন কী পদক্ষেপ নিতে হবে, তা নিয়ে একটি ব্লগ পোস্টের দ্বিতীয় পর্ব নিচে দেওয়া হলো।
ইউজার যখন অধৈর্য: ফেচ রিকোয়েস্ট বাতিল করা এবং পরবর্তী পদক্ষেপ (পার্ট-২)
প্রথম পর্বে আমরা দেখেছিলাম কীভাবে বারবার সাবমিট বোতামে ক্লিক করা থেকে অ্যাপকে রক্ষা করা যায়। কিন্তু যদি ফেচ রিকোয়েস্ট শুরু হওয়ার পর ব্যবহারকারী নিজেই অধৈর্য হয়ে যান? যদি ছবি জেনারেট হতে অনেক বেশি সময় লাগে এবং ব্যবহারকারী এই প্রক্রিয়াটি থামিয়ে দিতে চান?
এই পর্বে আমরা দেখব কীভাবে ব্যবহারকারীকে একটি "Cancel" বা "বাতিল করুন" বোতামের মাধ্যমে চলমান ফেচ অনুরোধ বাতিল করার ক্ষমতা দেওয়া যায় এবং বাতিলের পর আমরা কীভাবে সঠিক পদক্ষেপ নিতে পারি।
মূল হাতিয়ার: AbortController
🤖
ব্রাউজারে চলমান ফেচ অনুরোধ বাতিল করার জন্য স্ট্যান্ডার্ড API হলো AbortController
। এটি যেভাবে কাজ করে:
- তৈরি করা: প্রথমে আমরা
AbortController
-এর একটি ইন্সট্যান্স তৈরি করি। - সংকেত (Signal) নেওয়া: এই ইন্সট্যান্স থেকে আমরা একটি
signal
অবজেক্ট পাই। fetch
-কে জানানো: এইsignal
অবজেক্টটি আমরাfetch
অনুরোধের অপশন হিসেবে পাস করি।- বাতিল করা: যখন আমরা অনুরোধটি বাতিল করতে চাই, তখন কন্ট্রোলারের
abort()
মেথড কল করি। এটিfetch
-কে একটি সংকেত পাঠায় এবংfetch
তার কাজ থামিয়ে দেয়।
চলুন দেখি, আমাদের আগের কোডে এটি কীভাবে যুক্ত করা যায়।
সমস্যার সমাধান: কোডে পরিবর্তন 👨💻
আমরা আমাদের CreatePage
কম্পোনেন্টে একটি "Cancel" বাটন যোগ করব এবং AbortController
ব্যবহার করে ফেচ বাতিলের কার্যকারিতা যুক্ত করব।
ধাপ ১: AbortController
-কে ট্র্যাক করা
আমাদের AbortController
-এর ইন্সট্যান্সকে কম্পোনেন্টের মধ্যে ট্র্যাক করতে হবে যাতে আমরা যেকোনো সময় এটিকে অ্যাক্সেস করতে পারি। এর জন্য useRef
ব্যবহার করা সবচেয়ে ভালো উপায়, কারণ এটি রি-রেন্ডার ছাড়াই মান ধরে রাখতে পারে।
import { useEffect, useState, useRef } from "react"; // ✅ useRef ইমপোর্ট করুন
// ... বাকি কোড
export default function CreatePage() {
// ... অন্যান্য স্টেট
const [isFetching, setIsFetching] = useState(false);
const abortControllerRef = useRef(null); // ✅ AbortController ট্র্যাক করার জন্য ref
// ... বাকি কোড
}
ধাপ ২: ফেচ বাতিলের পর পদক্ষেপ নেওয়া
যখন fetch
অনুরোধ abort()
মেথডের মাধ্যমে বাতিল করা হয়, তখন এটি একটি Promise rejection
দেয় এবং catch
ব্লকে একটি নির্দিষ্ট নামের (AbortError
) এরর থ্রো করে। আমরা এই এররটি ধরে ব্যবহারকারীকে জানাতে পারি যে অনুরোধটি সফলভাবে বাতিল করা হয়েছে।
সংশোধিত কোড:
নিচে সম্পূর্ণ সংশোধিত কোড দেওয়া হলো, যেখানে ফেচ বাতিলের কার্যকারিতা যুক্ত করা হয়েছে।
import { useEffect, useState, useRef } from "react";
import { useImages } from "../../context/ImagesContext";
import getRandomNumber from "../../utils/getRandomNumber";
import AdvancedOptions from "./AdvancedOptions";
import DisplayImages from "./DiplayImages";
import InputPrompt from "./InputPrompt";
import WelcomeMessage from "./WelcomeMessage";
export default function CreatePage() {
const [apiParameters, setApiParameters] = useState({
promptText: "",
model: "flux",
height: "",
width: "",
});
const [models, setModels] = useState([]);
const [message, setMessage] = useState("");
const { images, setImages } = useImages("");
const [isFetching, setIsFetching] = useState(false);
const abortControllerRef = useRef(null); // ✅ AbortController ট্র্যাক করার জন্য ref
// ... মডেল ফেচ করার useEffect (অপরিবর্তিত)
// ✅ ফেচ বাতিল করার জন্য ফাংশন
function handleCancel() {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
setIsFetching(false);
console.log("Fetch requests cancelled by the user.");
}
}
async function handleSubmit() {
if (isFetching) {
return;
}
if (apiParameters?.model === "gptimage") {
setMessage(
`This selected Model ${apiParameters?.model} is for Paid User Only`
);
} else {
setIsFetching(true);
abortControllerRef.current = new AbortController(); // ✅ প্রতিটি সাবমিটে নতুন কন্ট্রোলার
console.log("fetching start");
setImages(() =>
Array(9)
.fill(null)
.map(() => ({
src: "",
seed: 0,
loading: "Generating...",
error: "",
}))
);
for (let i = 0; i < 9; i++) {
const seed = getRandomNumber();
const url = `https://image.pollinations.ai/prompt/${apiParameters?.promptText}?width=${apiParameters?.width}&height=${apiParameters?.height}&seed=${seed}&model=${apiParameters?.model}`;
try {
const response = await fetch(url, {
signal: abortControllerRef.current.signal, // ✅ সিগন্যাল পাস করা
});
if (!response.ok) {
// ... এরর হ্যান্ডলিং (অপরিবর্তিত)
}
const data = await response.blob();
const imageURL = URL.createObjectURL(data);
setImages((previous) => {
const newImages = [...previous];
newImages[i] = { src: imageURL, seed, loading: "", error: "" };
return newImages;
});
} catch (err) {
// ✅ বাতিল করা হলে নির্দিষ্ট পদক্ষেপ
if (err.name === "AbortError") {
setImages((previous) => {
const newImages = [...previous];
// যেসব ছবি লোড হচ্ছিল, সেগুলোকে বাতিল হিসেবে দেখানো
for (let j = i; j < 9; j++) {
if (newImages[j].loading) {
newImages[j] = {
src: "",
seed: 0,
loading: "",
error: "অনুরোধ বাতিল করা হয়েছে",
};
}
}
return newImages;
});
break; // ✅ বাতিল হলে লুপ থেকে বেরিয়ে আসা
} else {
// ... অন্যান্য এরর হ্যান্ডলিং (অপরিবর্তিত)
}
} finally {
if (i === 8) {
setIsFetching(false);
}
}
}
}
}
return (
<main className="relative z-10">
<WelcomeMessage message=" Let's create a masterpiece, Alvian!" />
<InputPrompt
onSubmitted={handleSubmit}
apiParameters={apiParameters}
setApiParameters={setApiParameters}
/>
{/* ✅ ফেচিং চলাকালীন বাতিল বাটন দেখানো */}
{isFetching && (
<button
onClick={handleCancel}
className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
>
বাতিল করুন
</button>
)}
{message && <p className="text-red-500">{message}</p>}
<AdvancedOptions
models={models}
setApiParameters={setApiParameters}
apiParameters={apiParameters}
/>
<DisplayImages />
</main>
);
}
পরিবর্তনগুলোর ব্যাখ্যা:
handleCancel
ফাংশন: এই ফাংশনটিabortControllerRef
-এ থাকা বর্তমান কন্ট্রোলারেরabort()
মেথডকে কল করে। এর ফলেfetch
অনুরোধটি বাতিল হয়ে যায়।handleSubmit
-এ পরিবর্তন:- প্রতিবার নতুন সাবমিট করার সময়
new AbortController()
দিয়ে একটি নতুন কন্ট্রোলার তৈরি করা হচ্ছে। fetch
-এর অপশনে{ signal: abortControllerRef.current.signal }
পাস করা হচ্ছে।
- প্রতিবার নতুন সাবমিট করার সময়
catch
ব্লকে পরিবর্তন:err.name === 'AbortError'
চেক করে আমরা নিশ্চিত হচ্ছি যে এররটি ব্যবহারকারীর বাতিলের কারণেই ঘটেছে।- যদি অনুরোধ বাতিল হয়, তাহলে আমরা UI-তে একটি বার্তা ("অনুরোধ বাতিল করা হয়েছে") দেখাচ্ছি এবং
break
ব্যবহার করেfor
লুপটি থামিয়ে দিচ্ছি যাতে আর কোনো অপ্রয়োজনীয় অনুরোধ না যায়।
- UI-তে বাতিল বাটন:
- JSX-এর মধ্যে
isFetching
true
থাকা অবস্থায় একটি "বাতিল করুন" বাটন দেখানো হচ্ছে। ব্যবহারকারী এই বাটনে ক্লিক করলেhandleCancel
ফাংশনটি কল হবে।
- JSX-এর মধ্যে
শেষ কথা
এই পরিবর্তনের মাধ্যমে আমরা ব্যবহারকারীকে আরও বেশি নিয়ন্ত্রণ দিয়েছি। এখন তারা শুধু একাধিকবার সাবমিট করা থেকেই বিরত থাকবে না, বরং চলমান কোনো দীর্ঘ প্রক্রিয়াকে নিজের ইচ্ছামতো থামিয়েও দিতে পারবে। এটি ব্যবহারকারীর অভিজ্ঞতাকে (User Experience) আরও উন্নত করে এবং আপনার অ্যাপ্লিকেশনকে আরও শক্তিশালী ও ব্যবহারকারী-বান্ধব করে তোলে।
Happy Coding! 🚀
Part 3
Md. Mojnu Miah ভাই, আপনি খুব ভালো প্রশ্ন করেছেন!
যদি আপনি for loop
দিয়ে ৯ বার image fetch করেন (যেমন আপনার আগের কোডে), এবং ইউজার চায় fetch চালাকালীন যেকোনো সময় Cancel করতে—তাহলে আপনাকে AbortController, useRef
, এবং cancel
বাটনের মাধ্যমে request cancel logic বানাতে হবে।
✅ লক্ষ্য:
- ইউজার Submit ক্লিক করলে ৯টি request পাঠানো হবে।
- ইউজার Cancel বাটনে ক্লিক করলে তখনই বাকি সব fetch বন্ধ হয়ে যাবে।
🧠 পরিকল্পনা:
- ✅
controllersRef
– সব AbortController রাখার জন্য। - ✅
isCancelled
– ইউজার cancel করলো কিনা সেটা ট্র্যাক করতে। - ✅ একটি Cancel Button যোগ করবো।
- ✅ fetch এর মাঝেই চেক করবো ইউজার cancel করলো কিনা।
✅ চূড়ান্ত উদাহরণ (React + Tailwind friendly)
import { useRef, useState } from "react";
import getRandomNumber from "./getRandomNumber";
export default function ImageFetcher() {
const [images, setImages] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const controllersRef = useRef([]);
const isCancelledRef = useRef(false);
const handleSubmit = async () => {
// Reset cancel state and abort old requests
isCancelledRef.current = false;
controllersRef.current.forEach((controller) => controller.abort());
controllersRef.current = [];
setIsFetching(true);
setImages(Array(9).fill({ loading: true }));
for (let i = 0; i < 9; i++) {
if (isCancelledRef.current) {
console.log("User cancelled, stopping remaining requests");
break;
}
const seed = getRandomNumber();
const controller = new AbortController();
controllersRef.current.push(controller);
const url = `https://image.pollinations.ai/prompt/football?width=256&height=256&seed=${seed}&model=flux`;
try {
const response = await fetch(url, {
signal: controller.signal,
});
if (!response.ok) throw new Error("Fetch failed");
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
setImages((prev) => {
const copy = [...prev];
copy[i] = { src: imageUrl, loading: false };
return copy;
});
} catch (err) {
setImages((prev) => {
const copy = [...prev];
copy[i] = { error: err.message, loading: false };
return copy;
});
}
}
setIsFetching(false);
};
const handleCancel = () => {
console.log("Cancelling...");
isCancelledRef.current = true;
controllersRef.current.forEach((controller) => controller.abort());
controllersRef.current = [];
setIsFetching(false);
};
return (
<div className="p-4">
<div className="space-x-4 mb-4">
<button
onClick={handleSubmit}
disabled={isFetching}
className="bg-blue-500 text-white px-4 py-2 rounded"
>
{isFetching ? "Generating..." : "Generate Images"}
</button>
{isFetching && (
<button
onClick={handleCancel}
className="bg-red-500 text-white px-4 py-2 rounded"
>
Cancel
</button>
)}
</div>
<div className="grid grid-cols-3 gap-4">
{images.map((img, idx) => (
<div
key={idx}
className="h-40 w-40 border flex items-center justify-center text-xs text-gray-500"
>
{img.loading ? (
"Loading..."
) : img.src ? (
<img src={img.src} className="object-cover h-full w-full" />
) : (
img.error || "Error"
)}
</div>
))}
</div>
</div>
);
}
✅ আপনি যা পাবেন:
- Submit → ৯টা ছবি লোড হবে।
- Cancel → সঙ্গে সঙ্গে সব বন্ধ হয়ে যাবে।
- ইউজার একাধিকবার Submit ক্লিক করলে আগের fetch বাতিল হবে।
🔄 পরবর্তী পদক্ষেপ:
- চাইলে Cancel button এ "Cancel & Reset" বা UI Reset অপশনও যোগ করতে পারেন।
- লোডিং স্পিনার/প্রোগ্রেস বার যোগ করতে চান?
বললে আমি সেটা সহ কোড আপডেট করে দিই।
আপনি কি এই Cancel ফিচার CreatePage
এ integrate করতে চান? আমি সেটাও করে দিতে পারি—step by step।