সরাসরি পর্বে যান
পর্ব ৯২ · ৯৩-এর মধ্যে · মডিউল ১৪

React আর frontend ইন্টারভিউ — ধারণা, কোড আর ডিবাগিং

React and Frontend Interviews — Concepts, Live Coding and Debugging

A preparation page in Bangla for React and frontend interviews: how rounds usually run, questions on rendering, reconciliation, keys, state, effects, memo, the React Compiler and Context, Next.js server components and hydration, forms, accessibility, performance, TypeScript and testing with good and weak answers, 11 live React blocks to predict, four live coding problems, six debugging bugs, a frontend system design of a product list page, and a final checklist.

এই পর্বে যা শিখবেন

  • Frontend ইন্টারভিউর সাধারণ ধাপগুলো চেনা, আর প্রতিটিতে কী দেখা হয় তা বোঝা
  • Rendering, reconciliation, key, state, effect, memo, Context, server component আর hydration-এর প্রশ্নে কারণসহ উত্তর দেওয়া
  • Debounced search, পাতা ভাগ করা তালিকা, যাচাইসহ form আর বাতিলযোগ্য useFetch কথা বলে বলে লেখা
  • ছয়টি সাধারণ React বাগ লক্ষণ থেকে ধরে কারণ প্রমাণ করা, তারপর ঠিক করা
  • একটি পণ্য তালিকার পাতার frontend নকশা সংখ্যা আর trade-off দিয়ে ব্যাখ্যা করা

এই পাতার প্রশ্ন সম্পর্কে: প্রশ্নগুলো এই কোর্সের ১ থেকে ৮৮ নম্বর পর্ব থেকে সাজানো। এগুলো কোনো নির্দিষ্ট প্রতিষ্ঠান, বিশ্ববিদ্যালয় বা নিয়োগ পরীক্ষা থেকে নেওয়া নয়। এ ধরনের প্রশ্ন frontend আর full-stack ইন্টারভিউতে প্রায়ই আসে, তবে আপনার ইন্টারভিউ ভিন্ন হতে পারে। Version-গুলো কোর্সের লেখার সময়ের: React 19.3.1, Next.js 16.4.4, Vitest 5.1.0. Live ব্লকগুলো React-এর production build-এ চলে, তাই সেখানে development-এর সতর্কবার্তা দেখা যায় না; সেগুলো আগের পর্বের সত্যিকারের run থেকে উদ্ধৃত।

দর্জির দোকানে নতুন কারিগর নিতে গেলে ওস্তাদ শুধু জিজ্ঞেস করেন না, “সেলাই কত রকম?” একটি কাপড় হাতে দিয়ে বলেন, “এই মাপে একটি পাঞ্জাবি কাটুন।” তারপর একটি ভুল সেলাই দেখিয়ে বলেন, “এটি কোথায় গোলমাল?” Frontend ইন্টারভিউও অনেকটা তেমন। সংজ্ঞা জানা দরকার, তবে আসল পরীক্ষা হলো আপনি কোড লিখতে লিখতে কীভাবে ভাবেন, আর ভাঙা কোড দেখে কীভাবে কারণ খোঁজেন।

১ · Frontend ইন্টারভিউ সাধারণত কীভাবে চলে

রিভিশন: পর্ব ১ · ফুল-স্ট্যাক কী, পর্ব ৮৮ · ক্যাপস্টোন, JavaScript coding ইন্টারভিউ

প্রতিষ্ঠান ভেদে ধাপ, সংখ্যা আর নাম আলাদা। ছোট দলে হয়তো একটি আলাপ আর একটি কাজ, বড় দলে কয়েকটি আলাদা দফা। নিচের ছবিটি একটি সাধারণ কাঠামো, কোনো নির্দিষ্ট প্রতিষ্ঠানের নিয়ম নয়। সব ধাপ সব জায়গায় থাকে না, আর ক্রমও বদলাতে পারে।

Frontend ইন্টারভিউর সাধারণ দফা

A common shape, not a rule: screening, concepts, live coding, take-home, debugging, frontend system design

Frontend ইন্টারভিউর ছয়টি সাধারণ দফা ১ · Screeningপরিচয়, প্রকল্প, কেন এই কাজ ২ · ধারণার প্রশ্নrender, key, state, effect: কারণসহ ৩ · Live codingকথা বলে একটি component, edge case ৪ · Take-home (কোথাও কোথাও)ছোট অ্যাপ, README, test, সিদ্ধান্তের কারণ ৫ · ডিবাগিংলক্ষণ → প্রমাণ → কারণ → সমাধান → test ৬ · Frontend system designrendering, data, cache, ছবি, a11y, budget
ছবি ৯২.১ — সবুজ দুটি দফায় হাতে কোড লিখতে হয়। ডটেড দাগের take-home সব জায়গায় থাকে না। আচরণগত প্রশ্ন আর backend-এর নকশা আছে পরের পর্বে।
দফাযা দেখা হয়দুর্বল ধরনভালো ধরন
Screeningআপনি কী বানিয়েছেন, কতটা নিজেটুলের লম্বা তালিকাএকটি প্রকল্প, একটি কঠিন সিদ্ধান্ত, তার ফল
ধারণাReact কেন এমন আচরণ করেসংজ্ঞা মুখস্থনিয়ম, তার কারণ, আর একটি উদাহরণ
Live codingচিন্তার ক্রম, ছোট ধাপে এগোনোচুপচাপ টাইপ, শেষে চালানোআগে চাহিদা মেলানো, তারপর সরল রূপ, তারপর edge case
Take-homeগোছানো কোড আর সিদ্ধান্তের লেখাঅনেক library, কোনো README নেইছোট পরিসর, test, “যা করিনি আর কেন”
ডিবাগিংঅনুমান না প্রমাণ“এখানে useMemo দিন”লক্ষণ মেলানো, log বা DevTools দিয়ে কারণ প্রমাণ
System designবড় ছবি আর trade-offএক টুলের নাম, কোনো সংখ্যা নেইচাহিদা, rendering, data, cache, budget, ঝুঁকি

Live coding-এর পরিবেশও আলাদা হয়: কোথাও browser-এর একটি online editor, কোথাও নিজের ল্যাপটপে Vite project, কোথাও শুধু কাগজ বা whiteboard. Internet আর AI সহকারী ব্যবহারের নিয়ম আগে জিজ্ঞেস করে নিন। নিয়ম না জেনে AI খুলে বসা সবচেয়ে সহজে আস্থা হারানোর পথ।

প্র ১“আপনার একটি React প্রকল্পের কথা বলুন।” — দুই মিনিটে কী বলবেন? (সহজ)

ভালো উত্তর: চার অংশে। কী সমস্যা, কার জন্য। আমার নিজের ভূমিকা কী ছিল। একটি কঠিন সিদ্ধান্ত, যেমন “cart-এর state Context-এ না রেখে server state টুলে রাখলাম, কারণ cart সার্ভারের ডেটা।” আর ফল কী হলো, সম্ভব হলে একটি মাপা সংখ্যা দিয়ে। নিজের ক্যাপস্টোন (পর্ব ৮৮) এই গল্পের সবচেয়ে সৎ উৎস।

দুর্বল উত্তর: “React, Redux, Tailwind, Next.js, Node দিয়ে একটি e-commerce বানিয়েছি।” টুলের নাম শুনে ইন্টারভিউয়ার আপনার চিন্তা জানতে পারেন না।

ফলো-আপ: “আবার বানালে কী বদলাতেন?” — একটি সত্যিকারের ভুল বলুন আর তা থেকে কী শিখেছেন, তা-ও।

প্র ২Live coding-এ প্রশ্ন পেয়েই কী করবেন? (সহজ)

ভালো উত্তর: প্রথম দুই-তিন মিনিট কোড নয়, প্রশ্ন। ইনপুট কী, আউটপুট কী, কোন অবস্থাগুলো দেখাতে হবে (loading, empty, error), keyboard-এ চলবে কি না, কতগুলো আইটেম। তারপর বলুন, “আগে সবচেয়ে সরল চলমান রূপ, তারপর edge case.” প্রতি ছোট ধাপে চালিয়ে দেখুন। ভুল হলে জোরে বলুন কী ভাবছেন।

দুর্বল উত্তর: সঙ্গে সঙ্গে টাইপ শুরু, বিশ মিনিট চুপ, তারপর একবারে চালানো। চলল না, অথচ ইন্টারভিউয়ার জানেন না আপনি কোথায় ছিলেন।

প্র ৩যে প্রশ্নের উত্তর জানেন না, তখন কী বলবেন? (সহজ)

ভালো উত্তর: তিন অংশে: যা জানি, যা জানি না, কীভাবে জানব। যেমন: “Partial prerendering-এর ভেতরের খুঁটিনাটি নিশ্চিত নই। জানি এটি static shell আগে পাঠায়, বাকিটা stream করে। নিশ্চিত হতে Next.js-এর নিজের version-এর নথি পড়ব, আর একটি ছোট পাতা build করে route তালিকা দেখব।” বানিয়ে বলা ধরা পড়ে প্রথম ফলো-আপেই।

দুর্বল উত্তর: আত্মবিশ্বাসী গলায় অনুমান, অথবা পুরো চুপ।

২ · React-এর ভিত্তি — render, reconciliation, key আর state

রিভিশন: পর্ব ৮, পর্ব ১০, পর্ব ১১, পর্ব ১২, পর্ব ১৫, পর্ব ১৬

এই অংশের প্রশ্নগুলো প্রায় সব frontend ইন্টারভিউর ভিত্তি। প্রতিটি উত্তরে একটি বাড়তি বাক্য রাখুন: “এটি কীভাবে যাচাই করা যায়।” এতে মুখস্থ উত্তর প্রমাণসহ উত্তর হয়ে ওঠে।

প্র ৪React-এ “render” মানে কী? Render হলেই কি DOM বদলায়? (সহজ)

ভালো উত্তর: Render মানে React আপনার component function ডেকে নতুন React element-এর গাছ পাওয়া। এটি শুধু হিসাব। তারপর reconciliation আগের গাছের সাথে মেলায়, আর commit ধাপে শুধু যা সত্যিই বদলেছে তা DOM-এ লেখে। তাই একটি component দশবার render হয়েও DOM একবারও না বদলাতে পারে। যাচাই: React DevTools-এর Profiler-এ render দেখা যায়, আর Elements প্যানেলে বদলানো node ঝলক দেয়।

দুর্বল উত্তর: “Render মানে পাতা নতুন করে আঁকা।” এই ভুল ধারণা থেকেই অকারণে memo ছড়ানো শুরু হয়।

প্র ৫একটি component কখন আবার render হয়? (সহজ)

ভালো উত্তর: তিন কারণে। নিজের state বদলালে (মান Object.is-এ আলাদা হলে)। Parent render হলে, props বদলাক বা না বদলাক, যদি না component-টি memo করা আর সব props এক থাকে। আর যে Context পড়ে তার মান বদলালে, memo থাকলেও। “Props বদলালে render হয়” কথাটি অর্ধেক সত্য: আসল কারণ parent-এর render.

দুর্বল উত্তর: “শুধু props বা state বদলালে।” তখন ফলো-আপ আসে: “Parent render হলো, props একই, child কি চলবে?” — হ্যাঁ, চলবে।

প্র ৬Virtual DOM আর reconciliation কী? সাধারণ গাছ মেলানো তো ধীর, React কীভাবে দ্রুত করে? (মাঝারি)

ভালো উত্তর: Virtual DOM মানে UI-এর হালকা JavaScript বর্ণনা। দুটি যেকোনো গাছ নিখুঁতভাবে মেলানোর সাধারণ পদ্ধতির খরচ প্রায় O(n³)। React দুটি heuristic ধরে O(n)-এ নামায়: এক, element-এর ধরন বদলালে (যেমন <div> থেকে <section>) পুরো উপগাছ ফেলে নতুন বানায়; দুই, একই স্তরের সন্তানদের key দিয়ে চেনে। তাই ধরন আর key-ই পরিচয়। ভেতরের কাজের একক হলো Fiber, যা কাজ ভাগ করে থামাতে আর অগ্রাধিকার দিতে পারে (পর্ব ১৫)।

দুর্বল উত্তর: “Virtual DOM আসল DOM-এর চেয়ে দ্রুত।” এটি সরাসরি তুলনা নয়। Virtual DOM একটি বাড়তি ধাপ; লাভ হলো ঘোষণামূলক কোড আর কম, গোছানো DOM বদল।

ফলো-আপ: “একই জায়গায় component-এর ধরন বদলালে state কী হয়?” — হারায়, কারণ পুরোনো উপগাছ unmount হয়।

প্র ৭List-এ key কেন লাগে? Index বা Math.random()-কে key করলে কী হয়? (সহজ)

ভালো উত্তর: Key বলে, “আগের render-এর এই সারি আর এখনকার এই সারি একই জিনিস।” Index key-তে পরিচয় হয় অবস্থান। তালিকার মাঝে বা ওপরে কিছু ঢুকলে, মুছলে বা sort করলে state আর DOM ভুল সারির সাথে থেকে যায়। অংশ ৭-এর বাগ ৪-এ এর সত্যিকারের ফল দেখবেন। Math.random() আরও খারাপ: প্রতি render-এ নতুন key, তাই প্রতিবার সব সারি unmount আর mount, input-এর লেখা আর focus হারায়। ঠিক key হলো ডেটার স্থির id. তালিকা কখনো না বদলালে index চলতে পারে।

দুর্বল উত্তর: “Warning সরাতে key লাগে।” Warning-টি শুধু dev build-এ আসে; আসল কারণ পরিচয়।

আউটপুট অনুমান: একই click-এ তিনটি setState

এটি খুব প্রচলিত ধরনের প্রশ্ন। কোড দেখিয়ে জিজ্ঞেস করা হয়, “বোতাম একবার চাপলে console-এ কী আসবে, আর পর্দায় কত দেখাবে?” নিচের ব্লক লোড হলেই বোতামটি একবার চাপার কাজ নিজে করে। চালানোর আগে উত্তর লিখে নিন।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
let click;
function Counter() {
  const [n, setN] = useState(0);
  console.log('render, n =', n);
  click = () => {
    setN(n + 1);
    setN(n + 1);
    setN(c => c + 1);
    console.log('handler-এর ভেতরে n =', n);
  };
  return <button onClick={click}>ঝুড়িতে {n}টি</button>;
}
createRoot(document.getElementById('root')).render(<Counter />);
setTimeout(() => click(), 50);
</script>

সত্যিকারের আউটপুট (React 19.3.1, এই পাতার live ব্লক):

render, n = 0
handler-এর ভেতরে n = 0
render, n = 2

তিনটি জিনিস একসাথে দেখা গেল। এক, snapshot: handler চলার পুরো সময় n হলো সেই render-এর মান, ০. তাই setN(n + 1) দুবার মানে দুবারই “মান হবে ১”. দুই, updater function c => c + 1 সারির আগের ফল থেকে হিসাব করে, তাই ১ থেকে ২. তিন, batching: তিনটি update-এর পরে render একবারই, render, n = 2. Handler-টি এখানে setTimeout থেকে ডাকা, তবু batch হয়েছে; React 18 থেকে এটি সব জায়গায় স্বয়ংক্রিয়।

Counter-এর update সারি
ধাপ
সারিতে
handler-এর n
পর্দায়
  1. প্রথম render. n = ০, console-এ render, n = 0. পর্দায় “ঝুড়িতে ০টি”.
  2. প্রথম update. n + 1 হিসাব হলো ০ + ১ = ১. React এখনই render করে না, সারিতে “১ বসানো” রাখে।
  3. দ্বিতীয় update. Handler-এর n এখনো ০, তাই আবারও “১ বসানো”. সারিতে দুটি একই কাজ।
  4. Updater. এবার মান নয়, function: “আগের ফলের সাথে ১ যোগ”. Handler শেষে console-এ handler-এর ভেতরে n = 0.
  5. সারি প্রক্রিয়া. ০ থেকে শুরু: প্রথমটিতে ১, দ্বিতীয়টিতে আবার ১, updater-এ ১ + ১ = ২. Render একবারই, render, n = 2.
  6. Commit. শুধু বোতামের লেখা বদলাল: “ঝুড়িতে ২টি”. তিনটি update, একটি render, একটি DOM বদল।
প্র ৮State কোথায় রাখবেন — component-এ, parent-এ, URL-এ, না global store-এ? (মাঝারি)

ভালো উত্তর: প্রশ্ন দিয়ে ঠিক করি। এটা কি সার্ভারের ডেটা? তাহলে server state, TanStack Query বা framework-এর data fetching (পর্ব ১৯)। Link শেয়ার করলে অবস্থা থাকা দরকার (filter, page, sort)? তাহলে URL state. শুধু একটি component-এর দরকার? সেখানেই, colocation. দুই ভাইবোনের দরকার? নিকটতম parent-এ তুলুন, lifting state up. অনেক দূরের অনেক component, আর ঘন ঘন বদলায়? তখন Zustand বা Redux Toolkit-এর মতো store (পর্ব ১৮)। ক্রম গুরুত্বপূর্ণ: নিচ থেকে ওপরে (পর্ব ১৬)।

দুর্বল উত্তর: “সব Redux-এ রাখি, এক জায়গায় থাকে।” তখন input-এর প্রতিটি অক্ষরে দূরের component-ও জাগে, আর সার্ভারের ডেটার cache, refetch, invalidation হাতে লিখতে হয়।

প্র ৯কার্টের মোট দাম কি আলাদা state-এ রাখবেন? (সহজ)

ভালো উত্তর: না। মোট দাম items থেকে হিসাব করা যায়, তাই এটি derived state. Render-এর সময় সরাসরি হিসাব করি: const total = items.reduce(…). আলাদা state রাখলে দুটি তথ্য দুই ঠিকানায়, আর একটিকে বদলাতে ভুলে গেলে পর্দায় ভুল দাম। হিসাব সত্যিই ভারী হলে মেপে তারপর useMemo. useEffect-এ setTotal করা আরও খারাপ: বাড়তি render, আর একবার পুরোনো দাম দেখা যায়।

দুর্বল উত্তর: “useEffect(() => setTotal(…), [items]) লিখব।” পর্ব ১৩-এর সত্যিকারের ESLint run (eslint-plugin-react-hooks 7.1.1) ঠিক এটিকেই error দেখায়: Calling setState synchronously within an effect can trigger cascading renders.

প্র ১০Controlled আর uncontrolled input-এর পার্থক্য কী? কখন কোনটি? (সহজ)

ভালো উত্তর: Controlled input-এর মান React-এর state-এ, value আর onChange দিয়ে। প্রতি অক্ষরে যাচাই, ফরম্যাট বা অন্য ঘর বদলানো সহজ। Uncontrolled input-এর মান DOM-এ থাকে, defaultValue আর submit-এ FormData দিয়ে পড়ি। বড় form-এ কম render, আর server action-এর সাথে স্বাভাবিকভাবে মেলে। React Hook Form মূলত uncontrolled পথে চলে বলেই দ্রুত (পর্ব ২১)। একটি ঘরে দুটি মিশিয়ে ফেলা, যেমন value কখনো undefined, dev build-এ সতর্কবার্তা দেয়।

দুর্বল উত্তর: “Controlled ভালো, uncontrolled পুরোনো।” দুটিই আজকের React-এ বৈধ পথ।

আরও গভীরে। React ২০১৩ সালে open source হয়, আর ২০১৭ সালে React 16-এ reconciliation-এর ভেতরটা নতুন করে লেখা হয় “Fiber” নামে, যাতে কাজ টুকরো করে থামানো আর অগ্রাধিকার দেওয়া যায়। Hook আসে React 16.8-এ, ২০১৯ সালে। Key আর ধরন দিয়ে O(n) মেলানোর দুই অনুমান React-এর পুরোনো reconciliation নথিতে স্পষ্ট লেখা ছিল; আজকের concurrent rendering, transition আর Suspense সবই Fiber-এর ওপর দাঁড়িয়ে।

৩ · Effect, memo, React Compiler আর Context

রিভিশন: পর্ব ১৩, পর্ব ১৪, পর্ব ১৭, পর্ব ১৮, পর্ব ১৯, পর্ব ২৩

এই অংশ থেকেই ডিবাগিং রাউন্ডের বেশিরভাগ বাগ আসে। তাই উত্তরে নিয়মের সাথে “না মানলে কী ভাঙে” বলুন।

প্র ১১useEffect কিসের জন্য? কখন effect লাগেই না? (মাঝারি)

ভালো উত্তর: Effect হলো React-এর বাইরের কোনো system-এর সাথে মিলিয়ে চলার জায়গা: network, timer, browser API, WebSocket, তৃতীয় পক্ষের widget. Render শেষে, commit-এর পরে চলে। Effect লাগে না: props বা state থেকে কিছু হিসাব করতে (render-এ হিসাব করুন), কোনো ঘটনার উত্তরে কাজ করতে (event handler-এ করুন, যেমন “অর্ডার চাপলে POST”), আর props বদলালে state শূন্য করতে (key দিয়ে component নতুন করে শুরু করুন)। React-এর নথির শিরোনামই এ কথা বলে: “You Might Not Need an Effect”.

দুর্বল উত্তর: “Component mount হলে কিছু করতে useEffect, class-এর componentDidMount-এর মতো।” Lifecycle দিয়ে ভাবলে dependency ভুল হয়; ভাবতে হয় “কিসের সাথে sync রাখছি।”

প্র ১২Cleanup কখন চলে? Development-এ effect দুবার চলে কেন? (মাঝারি)

ভালো উত্তর: Cleanup চলে দুই সময়ে: dependency বদলে effect আবার চলার ঠিক আগে (পুরোনো মান নিয়ে), আর component unmount হলে। পর্ব ১৩-এর সত্যিকারের ক্রম ছিল নতুন render, তারপর পুরোনো cleanup, তারপর নতুন setup. Development-এ StrictMode ইচ্ছা করে প্রতিটি effect-কে setup, cleanup, setup চালায়, যাতে cleanup ভুলে গেলে তখনই ধরা পড়ে, যেমন দুটি WebSocket সংযোগ বা দুটি interval. Production-এ এটি হয় না।

দুর্বল উত্তর: “দুবার চলা React-এর বাগ, StrictMode বন্ধ করে দিই।” সতর্কসংকেত বন্ধ করলে বাগ থেকে যায়, শুধু দেখা যায় না।

প্র ১৩Dependency array-তে কী থাকবে, কে ঠিক করে? (মাঝারি)

ভালো উত্তর: আপনি নয়, কোড। Effect-এর ভেতরে যে props, state বা সেগুলো থেকে বানানো মান পড়া হয়, সবই dependency array-তে যাবে। তুলনা হয় Object.is-এ, তাই প্রতি render-এ নতুন object বা function দিলে effect প্রতিবার চলে। চালানো কমাতে চাইলে dependency মুছবেন না; কোড বদলান: object effect-এর ভেতরে বানান, primitive মান দিন, updater function ব্যবহার করুন, বা ঘটনার অংশ useEffectEvent-এ সরান (React 19.2 থেকে)। exhaustive-deps নিয়মটি এ কাজ যন্ত্রে ধরে দেয়।

দুর্বল উত্তর: “শুধু mount-এ চালাতে চাইলে [] দিই, lint-এর সতর্কতা বন্ধ করি।” অংশ ৭-এর বাগ ১ আর ২ ঠিক এখান থেকে।

প্র ১৪Effect-এ fetch করলে race condition কীভাবে হয়, আর কীভাবে ঠেকাবেন? (মাঝারি)

ভালো উত্তর: id ১-এর অনুরোধ ধীর, id ২-এর দ্রুত। ব্যবহারকারী ১ থেকে ২-এ গেলেন, ২-এর উত্তর আগে এল, তারপর ১-এর দেরিতে আসা উত্তর state-এ বসে পর্দায় ভুল পণ্য। সমাধান cleanup-এ: AbortController দিয়ে পুরোনো অনুরোধ বাতিল, অথবা অন্তত একটি ignore বা live পতাকা, যাতে পুরোনো উত্তর বাদ যায়। অংশ ৬-এর সমস্যা ৪-এ এর চলমান রূপ আছে। বাস্তবে TanStack Query বা framework-এর data fetching এটি নিজেই সামলায় (পর্ব ১৯)।

দুর্বল উত্তর: “async/await দিলে ক্রম ঠিক থাকে।” না; দুটি আলাদা অনুরোধের উত্তরের ক্রম network ঠিক করে।

প্র ১৫memo, useMemo আর useCallback কখন ব্যবহার করবেন? (মাঝারি)

ভালো উত্তর: আগে মাপি, তারপর। memo একটি component-কে props না বদলালে বাদ দেয়। useMemo একটি ভারী হিসাবের ফল, আর useCallback একটি function-এর reference ধরে রাখে। শেষ দুটির বড় কাজ হলো memo করা child-এর props স্থির রাখা, বা effect-এর dependency স্থির রাখা। একটি অস্থির prop, যেমন inline style={{…}}, পুরো memo-র লাভ শেষ করে: পর্ব ২৩-এর live ব্লকে “unstable” কার্ড ৪ বার আর “stable” ১ বার render হয়েছিল। হালকা component-এ এগুলো দিলে props মেলানোর খরচই লাভ খেয়ে ফেলতে পারে।

দুর্বল উত্তর: “সব function-এ useCallback দিই, performance ভালো হয়।” Child memo না হলে useCallback একা কিছুই বাঁচায় না।

প্র ১৬React Compiler কী করে, আর কী করে না? (মাঝারি)

ভালো উত্তর: React Compiler একটি build-সময়ের টুল (প্রথম stable 1.0, অক্টোবর ২০২৫)। Component পড়ে নিজেই ঠিক করে কোন মান আর JSX জমা রাখবে, আর সেই কোড বসায়, অনেকটা হাতে লেখা useMemo, useCallback আর memo-র কাজ। পর্ব ২৩-এর আসল build-এ পুরো ul একবার বানিয়ে জমা রাখতে দেখা গেছে। কিন্তু কোডের অর্থ বদলায় না: handler যদি setCount(count + 1) লেখে, function-টি সত্যিই প্রতিবার বদলায়, তাই নিচের সব Row চলেছিল। Compiler Rules of React মানা কোড ধরে নেয়; render-এ mutation থাকলে সে সেই component বাদ দিতে পারে। আর সে মাপা বাঁচায় না।

দুর্বল উত্তর: “Compiler থাকলে performance নিয়ে আর ভাবতে হয় না।” ভারী তালিকা, বড় bundle আর ধীর network-এর সমস্যা compiler-এর এলাকার বাইরে।

প্র ১৭Context-এর খরচ কী? কখন Context যথেষ্ট নয়? (মাঝারি)

ভালো উত্তর: Context নিজে state রাখে না, শুধু একটি মান পৌঁছে দেয়। মান Object.is-এ বদলালে ওই context-এর প্রতিটি consumer আবার render হয়, সে মানের যে অংশই পড়ুক, আর memo তা আটকায় না। Provider-এ value={{ theme }} লিখলে প্রতি render-এ নতুন object, তাই সব consumer অকারণে চলে। সমাধান: আলাদা জিনিস আলাদা context-এ, যেমন state আর dispatch ভাগ, আর useMemo দিয়ে value স্থির। Theme, ভাষা, লগইন করা ব্যবহারকারীর মতো কম বদলানো মানে Context চমৎকার। প্রতি অক্ষরে বা প্রতি ফ্রেমে বদলানো মান, বা অংশবিশেষ বেছে পড়া দরকার হলে selector-ওয়ালা store ভালো (পর্ব ১৭, পর্ব ১৮)।

দুর্বল উত্তর: “Context দিয়েই state management করি, Redux আর লাগে না।” কথাটি কখনো সত্য, কিন্তু খরচ না বলে বললে বোঝা যায় খরচটি জানা নেই।

প্র ১৮Custom hook কী? Hook-এর নিয়ম দুটি কেন? (সহজ)

ভালো উত্তর: Custom hook হলো use দিয়ে শুরু একটি function, যা অন্য hook ব্যবহার করে logic ভাগ করে: useDebounce, useFetch. এটি state ভাগ করে না; প্রতিটি component নিজের আলাদা কপি পায়। নিয়ম দুটি (Rules of Hooks): hook শুধু component বা অন্য hook-এর শীর্ষে ডাকা, আর শর্ত, loop বা আগাম return-এর পরে নয়। কারণ React hook-গুলোকে নাম দিয়ে নয়, ডাকার ক্রম দিয়ে চেনে। একটি render-এ ক্রম বদলালে এক hook-এর state আরেকটির ঘরে চলে যায়।

দুর্বল উত্তর: “নিয়মটি React-এর রীতি, ESLint চায় বলে মানি।”

প্র ১৯পণ্যের তালিকা Redux-এ, Context-এ, না TanStack Query-তে রাখবেন? (মাঝারি)

ভালো উত্তর: পণ্যের তালিকা সার্ভারের ডেটা, তার আসল মালিক database. তাই server state টুল: TanStack Query বা Next.js-এর server component আর cache. এরা cache, deduplication, পুরোনো ডেটা দেখিয়ে পেছনে refetch, retry আর mutation-এর পরে invalidation দেয়। Redux-এ রাখলে এসব হাতে লিখতে হয়, যদি না RTK Query নেন। Context-এ রাখলে cache-এর কোনো নিয়মই নেই। এ নিয়ে সম্প্রদায়ে মত আলাদা আছে, তবে “server state আর client state আলাদা” এই ভাগ এখন ব্যাপকভাবে গৃহীত।

দুর্বল উত্তর: “useEffect-এ fetch করে Redux-এ dispatch করি।” চলে, কিন্তু race condition, cache আর loading-এর সব দায় আপনার।

৪ · Next.js — server component, hydration আর rendering

রিভিশন: পর্ব ৩, পর্ব ৫২, পর্ব ৫৩, পর্ব ৫৪, পর্ব ৫৫, পর্ব ৫৬

প্র ২০Server Component আর Client Component-এর পার্থক্য কী? (মাঝারি)

ভালো উত্তর: Server Component শুধু server-এ (বা build-এ) চলে। Database বা secret সরাসরি পড়তে পারে, তার কোড browser-এর bundle-এ যায় না, কিন্তু state, effect বা click handler রাখতে পারে না। Client Component-এর ফাইলের শুরুতে 'use client'; তার কোড browser-এ যায়, আর প্রথম HTML-এর জন্য server-এও একবার render হয়, তারপর hydrate. App Router-এ ডিফল্ট হলো server. নিয়ম: যতটা পারা যায় server-এ, শুধু interactive পাতা-টুকরো client-এ। পর্ব ৫২-এ markdown library server-এ রাখায় client bundle-এ তার খরচ শূন্য হয়েছিল।

দুর্বল উত্তর: “Client component শুধু browser-এ চলে, তাই SEO খারাপ।” না, client component-ও প্রথমে server-এ HTML হয়।

প্র ২১'use client' কি প্রতিটি component-এর লেবেল? Server থেকে client-এ কী পাঠানো যায়? (কঠিন)

ভালো উত্তর: না, এটি একটি সীমানা। যে ফাইলে লেখা, সেটি আর সেটি যা import করে সব client bundle-এ যায়। তাই সীমানা যত নিচে, bundle তত ছোট। Server থেকে client component-এ props যায় serialize হয়ে, তাই শুধু serializable মান: string, number, সাধারণ object, array, Date, Promise. সাধারণ function যায় না, তবে Server Action যায়। Client component-এর ভেতরে server component রাখতে চাইলে import নয়, children বা slot prop দিয়ে পাঠাতে হয়।

দুর্বল উত্তর: “সমস্যা হলে পাতার ওপরে 'use client' দিয়ে দিই।” তখন পুরো পাতা আর তার সব library browser-এ যায়, আর server-এর সুবিধা হারায়।

প্র ২২Hydration কী? “Hydration failed” error কেন আসে, আর কীভাবে ঠিক করবেন? (মাঝারি)

ভালো উত্তর: Hydration হলো server-এর পাঠানো HTML-এ browser-এ React-এর প্রাণ ফেরানো: event handler জোড়া আর state চালু, DOM নতুন করে না বানিয়ে। শর্ত একটাই: browser-এর প্রথম render হুবহু server-এর HTML-এর মতো হবে। না মিললে hydration mismatch. পর্ব ৫৬-এর সত্যিকারের run-এ (Next.js 16.4.4) কারণগুলো ছিল Date.now(), Math.random(), typeof window, locale আর time zone-এর ফরম্যাট, ভুল HTML nesting আর browser extension. সমাধান: মান server থেকে একবার ঠিক করে props-এ পাঠানো, locale আর time zone স্পষ্ট লেখা, browser-এর মান লাগলে mount-এর পরে effect-এ দেখানো, আর HTML nesting ঠিক করা। অংশ ৭-এর বাগ ৬-এ এর একটি মডেল আছে।

দুর্বল উত্তর: “suppressHydrationWarning দিয়ে দিই।” এটি শুধু একটি element-এর লেখার পার্থক্যের সতর্কতা চুপ করায়, যেমন একটি সময়ের লেখা। গাছের গঠনের পার্থক্য ঠিক করে না।

প্র ২৩CSR, SSR, SSG আর ISR — বাজারঘরের কোন পাতায় কোনটি? (মাঝারি)

ভালো উত্তর: “About” বা নীতির পাতা প্রায় বদলায় না: SSG, build-এ HTML. পণ্যের পাতা হাজারটি, দাম দিনে কয়েকবার বদলায়: ISR বা cache-সহ static, সময় বা ঘটনায় revalidate. খোঁজের ফলাফল প্রতিটি query-তে আলাদা: SSR বা streaming. লগইন করা ক্রেতার dashboard, যা search engine দেখবে না: CSR বা ব্যক্তিগত dynamic render. মাপকাঠি তিনটি: ডেটা কত ঘন বদলায়, ব্যক্তিভেদে আলাদা কি না, আর SEO কতটা লাগে (পর্ব ৫৫)।

দুর্বল উত্তর: “SSR সবসময় দ্রুত।” প্রতি অনুরোধে server-এর কাজ বাড়ে, আর TTFB ডেটার ধীরতার সাথে বাঁধা পড়ে।

প্র ২৪Server Action-এ কোন নিরাপত্তা নিজে লিখতে হয়? (মাঝারি)

ভালো উত্তর: Server Action আসলে একটি public endpoint, যাকে যে কেউ সরাসরি ডাকতে পারে। তাই প্রতিটির ভেতরে নিজে: input যাচাই (যেমন Zod), লগইন আর অনুমতি যাচাই, ডেটার মালিকানা যাচাই (অন্যের অর্ডার বদলানো যাবে না), আর ভুলের বার্তায় ভেতরের তথ্য না দেওয়া। Button লুকানো নিরাপত্তা নয় (পর্ব ৫৪, পর্ব ৪৮)।

দুর্বল উত্তর: “Form শুধু admin পাতায় আছে, তাই নিরাপদ।”

৫ · Form, accessibility, performance, TypeScript আর testing

রিভিশন: পর্ব ৬, পর্ব ২১, পর্ব ২৩, পর্ব ৭৪, পর্ব ৭৭, পর্ব ৮৪, HTML ও CSS-এর accessibility

প্র ২৫Checkout form-এ validation কোথায় করবেন — browser-এ, না server-এ? (সহজ)

ভালো উত্তর: দুই জায়গায়, দুই কারণে। Browser-এ যাচাই ক্রেতার সুবিধার জন্য: তাড়াতাড়ি, ঘরের পাশে বার্তা। Server-এ যাচাই নিরাপত্তার জন্য, কারণ যে কেউ browser এড়িয়ে সরাসরি অনুরোধ পাঠাতে পারে। এক Zod schema দুই দিকে ভাগ করে নিলে নিয়ম একবারই লেখা হয় (পর্ব ২১, পর্ব ২৭)। Server-এর ভুল আবার ঠিক ঘরে ফিরিয়ে দেখাতে হয়।

দুর্বল উত্তর: “Frontend-এ যাচাই করেছি, তাই backend-এ লাগবে না।” এটি নিরাপত্তার সবচেয়ে পুরোনো ভুলগুলোর একটি।

প্র ২৬<div onClick> দিয়ে বোতাম বানালে সমস্যা কী? একটি form-কে accessible করতে কী কী লাগে? (মাঝারি)

ভালো উত্তর: div-এ keyboard focus নেই, Enter বা Space-এ চলে না, আর screen reader একে বোতাম বলে না। আসল <button> এসব বিনা খরচে দেয়। Form-এ: প্রতিটি ঘরে জোড়া <label>, ভুল হলে aria-invalid আর aria-describedby দিয়ে বার্তা ঘরের সাথে জোড়া, বার্তা শুধু লাল রঙে নয়, লেখায়, ভুলের পরে focus প্রথম ভুল ঘরে, আর দৃশ্যমান focus রেখা। শুধু আইকনের বোতামে accessible name. পর্ব ৭৪-এর সত্যিকারের run-এ নামহীন আইকন বোতামের জন্য getByRole('button', { name: … }) ব্যর্থ হয়েছিল; test-এর ব্যর্থতাই ছিল accessibility-র ব্যর্থতা।

দুর্বল উত্তর: “role="button" দিয়ে দিলেই হবে।” Role দিলেও keyboard আর focus নিজে লিখতে হয়। প্রথম নিয়ম: আসল HTML element.

প্র ২৭Core Web Vitals কী কী, ভালোর সীমা কত, আর React অ্যাপে প্রতিটি কোথা থেকে খারাপ হয়? (মাঝারি)

ভালো উত্তর: LCP (সবচেয়ে বড় দৃশ্যমান অংশ আঁকা) ≤ ২.৫ সেকেন্ড, INP (ক্লিকের পরে পরের আঁকা) ≤ ২০০ ms, CLS (হঠাৎ সরে যাওয়া) ≤ ০.১, আসল ব্যবহারকারীর 75th percentile-এ (পর্ব ৮৪)। React-এ LCP খারাপ হয় বড় hero ছবি, client-এ দেরিতে আসা ডেটা আর বড় JS bundle-এ; INP খারাপ হয় ভারী handler, বড় তালিকার re-render আর long task-এ; CLS খারাপ হয় মাপহীন ছবি, দেরিতে ঢোকা ব্যানার আর font বদলে। INP ২০২৪ সালে FID-এর জায়গা নেয়।

দুর্বল উত্তর: “Lighthouse-এ ১০০ পেলেই হলো।” Lighthouse lab-এর মাপ, INP মাপে না; রায় field data-র।

প্র ২৮২,০০০ পণ্যের তালিকায় filter-এ টাইপ করলে লেখা আটকে যায়। কী কী করবেন? (মাঝারি)

ভালো উত্তর: আগে মাপি: React Profiler আর Performance প্যানেলে কোন render বা কাজ ভারী। তারপর ক্রম ধরে: Row-এর props স্থির করে memo (বা React Compiler); input-এর লেখা জরুরি রেখে তালিকাকে useDeferredValue বা startTransition দিয়ে কম জরুরি করা; পর্দায় যা দেখা যায় শুধু তা আঁকতে virtualization; আর filter সার্ভারে সরানো, যদি ডেটা আরও বড় হয়। প্রতিটির পরে আবার মাপি (পর্ব ২৩)।

দুর্বল উত্তর: “সব জায়গায় useMemo দেব।” কোনটি ধীর তা না জেনে।

প্র ২৯প্রথম লোডে JavaScript বেশি। Bundle কীভাবে ছোট করবেন? (মাঝারি)

ভালো উত্তর: আগে bundle analyzer বা treemap দিয়ে দেখি কে বড়। তারপর: route ধরে code splitting, কম ব্যবহৃত অংশ (যেমন chart বা rich editor) lazy আর Suspense দিয়ে পরে আনা, ভারী library-র হালকা বিকল্প বা server-এ সরানো (server component), tree shaking ভাঙে এমন barrel import এড়ানো, আর তৃতীয় পক্ষের script দেরিতে। মাপ হয় সংকুচিত আকারে, আর লক্ষ্য একটি budget (পর্ব ৭৯)।

দুর্বল উত্তর: “Minify করলেই হবে।” Build টুল এটি আগেই করে।

প্র ৩০React-এ TypeScript: fetch-এর অবস্থা কীভাবে type করবেন যাতে অসম্ভব অবস্থা না আসে? (মাঝারি)

ভালো উত্তর: তিনটি আলাদা boolean (isLoading, isError, data) না রেখে একটি discriminated union:

type Load<T> =
  | { status: 'loading' }
  | { status: 'error'; message: string }
  | { status: 'ok'; data: T };

status দেখে narrowing হয়, তাই 'ok' না হলে data পড়লে compiler আটকায়, আর “loading-ও, error-ও” অবস্থা লেখাই যায় না। Props-এ type ProductCardProps = { product: Product; onAdd: (id: string) => void }, event-এ React.ChangeEvent<HTMLInputElement>. মনে রাখি, type runtime-এ মুছে যায়, তাই বাইরের JSON যাচাই করতে Zod লাগে (পর্ব ৬)।

দুর্বল উত্তর: “ঝামেলা হলে any দিই।” তাহলে TypeScript যা দিত, তা ফেলে দিলেন।

প্র ৩১Testing Library দিয়ে component test-এর মূল দর্শন কী? কোন query আগে? (মাঝারি)

ভালো উত্তর: ব্যবহারকারী যেভাবে পাতা চেনেন, test-ও সেভাবে: role আর নাম দিয়ে খোঁজা, ক্লিক আর টাইপ করা, পর্দায় কী এল তা যাচাই। ভেতরের state বা কোন hook ডাকা হলো, তা নয়। Query-র ক্রম: getByRole, তারপর getByLabelText, getByText, আর শেষ ভরসা getByTestId. get না পেলে সঙ্গে সঙ্গে ব্যর্থ, query না পেলে null (না-থাকা যাচাইয়ে), find অপেক্ষা করে (async UI-তে)। ক্লিকে user-event, যা আসল ব্যবহারকারীর মতো ঘটনার ক্রম চালায় (পর্ব ৭৪)।

দুর্বল উত্তর: “প্রতিটি component-এর snapshot test রাখি।” Snapshot বড় হলে কেউ পড়ে না, আর সব বদলেই ভাঙে।

প্র ৩২Test-এ network কীভাবে সামলাবেন? “এটি implementation detail” মানে কী? (মাঝারি)

ভালো উত্তর: MSW দিয়ে network স্তরে নকল উত্তর, যাতে component-এর আসল fetch কোড চলে আর আমরা সফল, ধীর আর ব্যর্থ, তিন উত্তরই পরীক্ষা করি (পর্ব ৭৭)। Implementation detail মানে ব্যবহারকারী যা দেখেন না: state-এর নাম, কোন hook, কতবার render. এগুলো test করলে refactor-এ test ভাঙে অথচ আচরণ ঠিক থাকে, আর সেই test আস্থা হারায়।

দুর্বল উত্তর: “নিজের fetch function mock করে দেখি সেটি ডাকা হয়েছে কি না।” ডাকা হলো, কিন্তু পর্দায় ঠিক জিনিস এল কি না, তা জানা গেল না।

প্র ৩৩dangerouslySetInnerHTML কখন বিপদ, আর React নিজে কী রক্ষা দেয়? (মাঝারি)

ভালো উত্তর: JSX-এ {text} লিখলে React লেখাটি escape করে, তাই ব্যবহারকারীর লেখা HTML বা script হয়ে চলে না। dangerouslySetInnerHTML এই সুরক্ষা বন্ধ করে। পণ্যের রিভিউর মতো বাইরের লেখা সেখানে দিলে XSS. দিতেই হলে আগে DOMPurify-এর মতো sanitizer, আর সাথে CSP. আরেকটি ফাঁক: href-এ ব্যবহারকারীর দেওয়া URL, যেখানে javascript: ধরনের লিংক আটকাতে protocol যাচাই লাগে (পর্ব ৫০, XSS আর CSP).

দুর্বল উত্তর: “React ব্যবহার করলে XSS হয় না।”

৬ · Live coding — চারটি সমস্যা, ধাপে ধাপে

রিভিশন: পর্ব ১৪, পর্ব ১১, পর্ব ১২, পর্ব ১৩, পর্ব ৩৪

প্রতিটি সমস্যায় একই ছক: চাহিদা মেলানো, সরল রূপ, তারপর edge case. ব্লকগুলো লোড হলেই একজন কাল্পনিক ব্যবহারকারীর টাইপ আর ক্লিক নিজে চালায় (সময় হিসাব করা setTimeout দিয়ে), যাতে আউটপুট প্রতিবার একই আসে। পর্দার preview-তে আপনি নিজেও টাইপ বা ক্লিক করতে পারবেন। প্রতিটি ৩০ লাইনের মধ্যে রাখা হয়েছে; ইন্টারভিউতে আপনার কোড একটু লম্বা হলেও ক্ষতি নেই।

সমস্যা ১ · Debounced search

প্রশ্ন: “বাজারঘরের খোঁজের ঘরে প্রতিটি অক্ষরে API ডাকা হচ্ছে। টাইপ থামার ৩০০ ms পরে একবার ডাকুন।”

যা জিজ্ঞেস করবেন: খালি লেখায় কী দেখাবে? শুরু-শেষের ফাঁকা জায়গা বাদ যাবে কি? দ্রুত টাইপের মাঝে পুরোনো অনুরোধের উত্তর এলে কী হবে?

পরিকল্পনা জোরে বলুন: “লেখাটি state-এ থাকবে, যাতে input সঙ্গে সঙ্গে সাড়া দেয়। একটি useDebounce hook সেই মানের দেরি করা কপি দেবে। প্রতি অক্ষরে একটি timer, আর cleanup-এ আগেরটি বাতিল। API-র কাজ দেরি করা মানের ওপর চলবে।”

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
const PRODUCTS = ['জামদানি শাড়ি', 'শাড়ির পাড়', 'শাল', 'লুঙ্গি', 'শীতের চাদর'];
function useDebounce(value, ms) {
  const [v, setV] = useState(value);
  useEffect(() => {
    const t = setTimeout(() => setV(value), ms);
    return () => clearTimeout(t);   // নতুন অক্ষর এলে আগের timer বাতিল
  }, [value, ms]);
  return v;
}
let type;
function Search() {
  const [q, setQ] = useState('');
  type = setQ;
  const dq = useDebounce(q.trim(), 300);
  useEffect(() => { if (dq) console.log('API-তে খোঁজ:', dq); }, [dq]);
  const hits = dq ? PRODUCTS.filter(p => p.includes(dq)) : [];
  return (<div>
    <label>পণ্য খুঁজুন <input value={q} onChange={e => setQ(e.target.value)} /></label>
    <ul aria-live="polite">{hits.map(h => <li key={h}>{h}</li>)}</ul>
  </div>);
}
createRoot(document.getElementById('root')).render(<Search />);
['শ', 'শা', 'শাড়', 'শাড়ি'].forEach((s, i) => setTimeout(() => type(s), 50 + i * 80));
setTimeout(() => console.log('তালিকা:', [...document.querySelectorAll('li')]
  .map(li => li.textContent).join(', ')), 900);
</script>

সত্যিকারের আউটপুট:

API-তে খোঁজ: শাড়ি
তালিকা: জামদানি শাড়ি, শাড়ির পাড়

চারটি অক্ষর এল ৫০, ১৩০, ২১০ আর ২৯০ ms-এ, কিন্তু “API-তে খোঁজ” এল একবারই, শেষ লেখার জন্য। প্রতিটি নতুন অক্ষরে value বদলায়, effect-এর cleanup আগের timer বাতিল করে, আর নতুন ৩০০ ms গোনা শুরু হয়। Input-এ লেখা কিন্তু দেরি করেনি, কারণ q আলাদা। নিচের stepper-এ পুরো সময়রেখা।

useDebounce(q, 300)
সময়
q (input)
চালু timer
dq (দেরি করা)
API ডাক
  1. প্রথম অক্ষর. q হলো “শ”, input সঙ্গে সঙ্গে দেখায়। Effect timer A চালায়, বাজবে ৫০ + ৩০০ = ৩৫০ ms-এ।
  2. দ্বিতীয় অক্ষর, ৮০ ms পরে. value বদলাল, তাই effect আবার চলার আগে cleanup: A বাতিল। নতুন timer B, বাজবে ৪৩০-এ।
  3. তৃতীয় অক্ষর. একই ঘটনা: B বাতিল, C চালু, বাজবে ৫১০-এ। এখনো একটিও API ডাক নেই।
  4. শেষ অক্ষর. C বাতিল, D চালু, বাজবে ২৯০ + ৩০০ = ৫৯০-এ। এরপর আর কোনো অক্ষর নেই।
  5. D বাজল. setV('শাড়ি'), তাই dq বদলাল, আর [dq]-এর effect একবার “API-তে খোঁজ: শাড়ি” লিখল। চারটি অক্ষর, একটি ডাক।
প্র ৩৪ফলো-আপ: debounce আর throttle-এর পার্থক্য কী? দেরি করা ডাকের পুরোনো উত্তর দেরিতে এলে? (মাঝারি)

উত্তর: Debounce থামার পরে একবার চালায়: খোঁজ, auto-save. Throttle নির্দিষ্ট সময়ে সর্বোচ্চ একবার, থামা পর্যন্ত অপেক্ষা না করে: scroll, resize. দেরি করা হলেও দুটি অনুরোধ একসাথে চলতে পারে, যেমন “শাল” আর “শাড়ি”, তাই আসল fetch-এ সমস্যা ৪-এর মতো AbortController লাগবে। আরেকটি পথ: useDeferredValue, যা timer নয়, React-এর অগ্রাধিকার দিয়ে কাজ করে; সেটি local তালিকা ছাঁকতে ভালো, network ডাক কমাতে নয়।

সমস্যা ২ · পাতা ভাগ করা তালিকা, loading আর error-সহ

প্রশ্ন: “প্রতি পাতায় ৩টি পণ্য। ‘পরের’ বোতাম, লোডের বার্তা, আর server ভুল করলে ‘আবার চেষ্টা’।”

পরিকল্পনা: “তিনটি আলাদা boolean নয়। একটি ফল রাখব, যার সাথে লেখা থাকবে সেটি কোন অনুরোধের (পাতা আর চেষ্টার নম্বর)। এখনকার অনুরোধের সাথে না মিললেই loading. এতে effect-এর ভেতরে সঙ্গে সঙ্গে setLoading(true) লাগে না, আর পুরোনো উত্তর বাদ দিতে cleanup-এ একটি পতাকা।” নিচে পাতা ৩ প্রথমবার ইচ্ছা করে 503 দেয়।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
const ALL = Array.from({ length: 8 }, (_, i) => 'পণ্য ' + (i + 1));
let act, failOnce = true;   // পাতা ৩ প্রথমবার ব্যর্থ হবে
const api = page => new Promise((ok, fail) => setTimeout(() => page === 3 && failOnce
  ? (failOnce = false, fail(new Error('সার্ভার 503'))) : ok(ALL.slice(page * 3 - 3, page * 3)), 60));
function List() {
  const [page, setPage] = useState(1), [tries, setTries] = useState(0);
  const [res, setRes] = useState(null);       // { key, items } বা { key, error }
  act = { next: () => setPage(p => p + 1), retry: () => setTries(t => t + 1) };
  useEffect(() => {
    let live = true;                           // পুরোনো উত্তর এলে বাদ
    const key = page + ':' + tries;
    api(page).then(items => live && setRes({ key, items }),
      e => live && setRes({ key, error: e.message }));
    return () => { live = false; };
  }, [page, tries]);
  const loading = res?.key !== page + ':' + tries;
  const text = loading ? 'লোড হচ্ছে…' : res.error ? 'ভুল: ' + res.error : res.items.join(', ');
  useEffect(() => console.log('পাতা ' + page + ' → ' + text), [page, text]);
  return (<div>
    <p role={!loading && res.error ? 'alert' : 'status'}>{text}</p>
    {!loading && res.error && <button onClick={act.retry}>আবার চেষ্টা</button>}
    <button disabled={loading || !!res.error || res.items.length < 3} onClick={act.next}>পরের</button>
  </div>);
}
createRoot(document.getElementById('root')).render(<List />);
[[150, 'next'], [300, 'next'], [450, 'retry']].forEach(([ms, a]) => setTimeout(() => act[a](), ms));
</script>

সত্যিকারের আউটপুট:

পাতা 1 → লোড হচ্ছে…
পাতা 1 → পণ্য 1, পণ্য 2, পণ্য 3
পাতা 2 → লোড হচ্ছে…
পাতা 2 → পণ্য 4, পণ্য 5, পণ্য 6
পাতা 3 → লোড হচ্ছে…
পাতা 3 → ভুল: সার্ভার 503
পাতা 3 → লোড হচ্ছে…
পাতা 3 → পণ্য 7, পণ্য 8

তিনটি অবস্থাই দেখা গেল, আর “আবার চেষ্টা” tries বাড়িয়ে একই পাতার নতুন অনুরোধ পাঠাল। শেষ পাতায় ২টি পণ্য, তাই items.length < 3 শর্তে “পরের” বন্ধ। ভুলের বার্তায় role="alert", লোডে role="status", যাতে screen reader-ও বদল ঘোষণা করে। ইন্টারভিউতে বলুন: বাস্তবে মোট সংখ্যা বা hasMore server থেকে আসবে, বড় তালিকায় cursor pagination (পর্ব ৩৪), আর পাতা নম্বর URL-এ রাখলে link শেয়ার করা যায়।

প্র ৩৫ফলো-আপ: TanStack Query দিয়ে লিখলে কী বদলাবে? (মাঝারি)

উত্তর: পুরো effect চলে যাবে: useQuery({ queryKey: ['products', page], queryFn: () => api(page) }). Loading, error, retry আর পুরোনো উত্তর বাদ দেওয়া library করবে। পাতা বদলের সময় আগের পাতা দেখিয়ে রাখতে placeholder data, আর পরের পাতা আগাম আনতে prefetch. একই পাতায় ফিরে এলে cache থেকে সঙ্গে সঙ্গে দেখায় (পর্ব ১৯)। হাতে লেখার মূল্য হলো আপনি জানেন library ভেতরে কী সামলাচ্ছে।

সমস্যা ৩ · যাচাইসহ controlled form

প্রশ্ন: “মোবাইল নম্বর (01 দিয়ে শুরু, ১১ অঙ্ক) আর পরিমাণ (১ থেকে ৫)। প্রথম submit-এর আগে লাল দাগ দেখাবেন না। ভুল থাকলে পাঠাবেন না।”

পরিকল্পনা: “ভুলগুলো state নয়, form-এর মান থেকে হিসাব করা মান। শুধু ‘একবার চেষ্টা হয়েছে কি না’ আলাদা state. প্রতিটি ঘরে label, aria-invalid আর বার্তার সাথে aria-describedby.” ব্লকটি তিনবার submit করে: খালি, ভুল মান, আর ঠিক মান।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
const rules = {
  phone: v => /^01[3-9][0-9]{8}$/.test(v) ? '' : 'মোবাইল 01 দিয়ে শুরু, মোট ১১ অঙ্ক',
  qty: v => /^[1-5]$/.test(v) ? '' : 'পরিমাণ ১ থেকে ৫',
};
let fill, send;
function Checkout() {
  const [form, setForm] = useState({ phone: '', qty: '1' });
  const [tried, setTried] = useState(false);   // প্রথম submit-এর আগে লাল দাগ নয়
  const errors = Object.fromEntries(Object.keys(rules).map(k => [k, rules[k](form[k].trim())]));
  const bad = Object.values(errors).filter(Boolean);
  const onChange = e => setForm(f => ({ ...f, [e.target.name]: e.target.value }));
  fill = patch => setForm(f => ({ ...f, ...patch }));
  send = e => {
    e?.preventDefault(); setTried(true);
    console.log(bad.length ? 'আটকে গেল: ' + bad.join(' │ ') : 'পাঠানো হলো: ' + JSON.stringify(form));
  };
  const field = (name, label) => (<p>
    <label htmlFor={name}>{label}</label> <input id={name} name={name} value={form[name]} onChange={onChange}
      aria-invalid={tried && !!errors[name]} aria-describedby={name + '-err'} />
    <span id={name + '-err'}>{tried && errors[name]}</span></p>);
  return <form onSubmit={send} noValidate>{field('phone', 'মোবাইল')}{field('qty', 'পরিমাণ')}<button>অর্ডার করুন</button></form>;
}
createRoot(document.getElementById('root')).render(<Checkout />);
[() => send(), () => fill({ phone: '0171234567', qty: '9' }), () => send(),
  () => fill({ phone: '01712345678', qty: '2' }), () => send()].forEach((f, i) => setTimeout(f, 50 + i * 60));
</script>

সত্যিকারের আউটপুট:

আটকে গেল: মোবাইল 01 দিয়ে শুরু, মোট ১১ অঙ্ক
আটকে গেল: মোবাইল 01 দিয়ে শুরু, মোট ১১ অঙ্ক │ পরিমাণ ১ থেকে ৫
পাঠানো হলো: {"phone":"01712345678","qty":"2"}

প্রথম submit-এ পরিমাণের ডিফল্ট “1” ঠিক, তাই শুধু মোবাইলের ভুল। দ্বিতীয়টিতে ১০ অঙ্কের নম্বর আর “9”, দুটিই আটকাল। তৃতীয়টি পাঠানো হলো। ভুলের তালিকা render-এর সময়ই হিসাব হয়, তাই আলাদা errors state আর তাকে sync রাখার effect লাগেনি। noValidate browser-এর নিজের বুদবুদ বার্তা বন্ধ করে, যাতে বার্তা এক রকম দেখায়; <button>-এর ডিফল্ট type submit, তাই Enter চাপলেও জমা হয়।

প্র ৩৬ফলো-আপ: কেউ বাংলা অঙ্কে “০১৭১২৩৪৫৬৭৮” লিখলে কী হবে? আর ঘর বাড়লে কী করবেন? (মাঝারি)

উত্তর: এই regex-এ [0-9] শুধু Latin অঙ্ক মেলায়, তাই বাংলা অঙ্ক আটকে যাবে, যদিও নম্বর ঠিক। বাংলাভাষী ক্রেতার জন্য আগে অঙ্ক Latin-এ বদলে তারপর যাচাই করা ভালো, আর একই বদল server-এও (পর্ব ৫৯)। inputMode="numeric" দিলে ফোনে সংখ্যার কীবোর্ড আসে। ঘর দশটির বেশি হলে React Hook Form আর এক Zod schema, যা server-ও ব্যবহার করবে; তখন প্রতি অক্ষরে পুরো form-এর render-ও কমে (পর্ব ২১)। ভুলের পরে focus প্রথম ভুল ঘরে নেওয়াও যোগ করবেন।

সমস্যা ৪ · বাতিলযোগ্য useFetch

প্রশ্ন: “একটি useFetch(url) hook লিখুন, যা loading, data আর error দেয়। URL দ্রুত বদলালে পুরোনো উত্তর যেন পর্দায় না বসে।”

পরিকল্পনা: “প্রতিটি effect-এ একটি নতুন AbortController, cleanup-এ abort(). Abort-এর ভুলকে আসল ভুল ধরব না। fetch 404 বা 500-এ নিজে ভুল ছোড়ে না, তাই r.ok দেখব।” Browser-এর আসল fetch live ব্লকে network পায় না, তাই একই চুক্তির একটি নকল fakeFetch, যা signal মানে। পণ্য ১-এর উত্তর ধীর (২০০ ms), আর ৩০ ms পরেই id বদলে ২ হয়।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
function fakeFetch(url, { signal }) {             // পণ্য ১-এর উত্তর ধীর: 200 ms
  return new Promise((ok, fail) => {
    const t = setTimeout(() => ok({ ok: true, json: async () => ({ name: 'পণ্য ' + url.at(-1) }) }), url.endsWith('/1') ? 200 : 50);
    signal.addEventListener('abort', () => { clearTimeout(t); fail(new DOMException('বাতিল', 'AbortError')); });
  });
}
function useFetch(url) {
  const [state, setState] = useState({ url: null });
  useEffect(() => {
    const ctrl = new AbortController();
    fakeFetch(url, { signal: ctrl.signal })
      .then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .then(data => setState({ url, data }))
      .catch(e => e.name === 'AbortError' ? console.log('বাতিল:', url) : setState({ url, error: e.message }));
    return () => ctrl.abort();                     // url বদলালে বা unmount হলে
  }, [url]);
  return { ...state, loading: state.url !== url };
}
function Product({ id }) {
  const { data, error, loading } = useFetch('/api/products/' + id);
  const text = loading ? 'লোড হচ্ছে…' : error || data.name;
  useEffect(() => console.log('id ' + id + ' → ' + text), [id, text]);
  return <p>{text}</p>;
}
const root = createRoot(document.getElementById('root')); root.render(<Product id={1} />);
setTimeout(() => root.render(<Product id={2} />), 30);
</script>

সত্যিকারের আউটপুট:

id 1 → লোড হচ্ছে…
id 2 → লোড হচ্ছে…
বাতিল: /api/products/1
id 2 → পণ্য 2

ক্রমটি লক্ষ করুন। id বদলানোর commit-এ আগে পুরোনো effect-এর cleanup চলে, তাই abort(), তারপর নতুন effect. Promise-এর বাতিল-ভুল একটু পরে microtask-এ পৌঁছায়, তাই “বাতিল” লেখাটি “id 2 → লোড হচ্ছে…”-এর পরে এসেছে। পণ্য ১-এর উত্তর কখনো state-এ বসেনি। loading আলাদা state নয়, “শেষ ফলটি কি এই URL-এর?” প্রশ্নের উত্তর।

প্র ৩৭ফলো-আপ: এই hook production-এ কেন যথেষ্ট নয়? (কঠিন)

উত্তর: Cache নেই, তাই একই পণ্যে ফিরে এলে আবার অনুরোধ। দুই component একই URL চাইলে দুটি অনুরোধ, deduplication নেই। Retry, পুরোনো ডেটা দেখিয়ে পেছনে refetch, mutation-এর পরে invalidation, Suspense, server-এ আগাম ডেটা: কিছুই নেই। এগুলোর জন্য TanStack Query বা framework-এর data fetching. React 19-এর use একটি Promise পড়তে পারে, কিন্তু Promise-টি প্রতি render-এ নতুন বানালে চলবে না; সেটি cache-ওয়ালা উৎস থেকে আসতে হয়। Interview-তে এ সীমা নিজে বলা একটি শক্ত সংকেত।

৭ · ডিবাগিং রাউন্ড — ছয়টি সাধারণ বাগ

রিভিশন: পর্ব ১৩, পর্ব ১০, পর্ব ১১, পর্ব ৫৬, পর্ব ৮৩

ডিবাগিং রাউন্ডে সাধারণত একটি ছোট component আর একটি অভিযোগ দেওয়া হয়: “টাইমার আটকে যায়”, “ভুল পণ্য দেখায়”. ভালো উত্তরের ক্রম সবসময় এক: লক্ষণ মিলিয়ে নিই, ছোট করে আবার ঘটাই, একটি অনুমান বলি, log বা DevTools দিয়ে প্রমাণ করি, ঠিক করি, আর একটি test দিয়ে আটকে রাখি। প্রতিটি ব্লকে বাগওয়ালা আর ঠিক করা রূপ পাশাপাশি চলে।

বাগ ১ · Stale closure: টাইমার ১-এ আটকে থাকে

লক্ষণ: “অফারের সময় গোনার টাইমার এক সেকেন্ড দেখায়, তারপর থেমে যায়। কোনো error নেই।”

কারণ খোঁজা: Interval-এর ভেতরে console.log(sec) দিলে প্রতিবার ০ আসে। মানে callback-টি প্রথম render-এর মান ধরে আছে। Dependency [], তাই effect আর চলে না, callback-ও নতুন হয় না।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
function BugTimer() {
  const [sec, setSec] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setSec(sec + 1), 100);   // sec এখানে চিরকাল 0
    return () => clearInterval(id);
  }, []);
  return <p id="bug">{sec}</p>;
}
function FixTimer() {
  const [sec, setSec] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setSec(s => s + 1), 100); // সবসময় সর্বশেষ মান থেকে
    return () => clearInterval(id);
  }, []);
  return <p id="fix">{sec}</p>;
}
const root = createRoot(document.getElementById('root'));
root.render(<><BugTimer /><FixTimer /></>);
setTimeout(() => {
  const read = id => document.getElementById(id).textContent;
  console.log('৫৫০ ms পরে → bug:', read('bug'), '│ fix:', read('fix'));
  root.unmount();
}, 550);
</script>

সত্যিকারের আউটপুট:

৫৫০ ms পরে → bug: 1 │ fix: 5

বাগওয়ালা রূপ প্রতি ১০০ ms-এ setSec(0 + 1) চালায়: প্রথমবার ০ থেকে ১, তারপর প্রতিবার আবার ১, যা আগের মানের সমান, তাই React আর render-ই করে না। ঠিক রূপে updater s => s + 1 সবসময় সর্বশেষ মান থেকে হিসাব করে, তাই পাঁচটি tick-এ ৫. এখানে interval-কে sec জানতেই হয় না, তাই dependency [] সত্যিই ঠিক।

BugTimer আর FixTimer
সময়
bug: callback-এর sec
bug: নতুন মান
fix: updater
  1. প্রথম render. দুটি component-এ sec = ০. Effect একবার চলে, interval তৈরি হয়। বাগওয়ালা callback-টি এই render-এর closure, তাই তার ভেতরে sec চিরকাল ০.
  2. প্রথম tick. Bug: setSec(0 + 1), মান ১, render. Fix: updater ০ থেকে ১. এই মুহূর্তে দুটিই ১ দেখায়, তাই ছোট পরীক্ষায় বাগ লুকিয়ে থাকে।
  3. দ্বিতীয় tick. Bug-এর callback আবার 0 + 1 = ১ পাঠায়। আগের মানের সমান, তাই React render বাদ দেয়। Fix ১ থেকে ২.
  4. পরের tick-গুলো. Bug একই ১-এ আটকে। Fix প্রতিবার সারির শেষ মান থেকে এক বাড়ায়: ৩, ৪, ৫.
  5. ৫৫০ ms. Console-এ bug ১, fix ৫. বিকল্প সমাধান: dependency-তে sec দেওয়া, তখন প্রতি সেকেন্ডে interval ভেঙে নতুন হয়; কাজ করে, তবে updater-ই সরল।

বাগ ২ · Missing dependency: অন্য পণ্যে গেলেও পুরোনো নাম

লক্ষণ: “পণ্যের তালিকা থেকে দ্বিতীয় পণ্যে গেলাম, URL বদলাল, কিন্তু বিবরণে প্রথম পণ্যের নাম।”

কারণ খোঁজা: Effect-এর ভেতরে console.log('load', id) দিলে শুধু একবার load 1 আসে। Effect id পড়ে, অথচ তালিকায় নেই। ESLint-এর react-hooks/exhaustive-deps ঠিক এটিই বলত: পর্ব ১৩-এর সত্যিকারের run-এ এসেছিল React Hook useEffect has a missing dependency: 'userId'.

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
const db = { 1: 'জামদানি শাড়ি', 2: 'নকশিকাঁথা' };
const load = id => new Promise(ok => setTimeout(() => ok(db[id]), 20));
function BugDetail({ id }) {
  const [name, setName] = useState('…');
  useEffect(() => { load(id).then(setName); }, []);   // id ব্যবহার হলো, তালিকায় নেই
  return <p>bug: id {id} → {name}</p>;
}
function FixDetail({ id }) {
  const [name, setName] = useState('…');
  useEffect(() => {
    let live = true;
    load(id).then(n => { if (live) setName(n); });
    return () => { live = false; };
  }, [id]);
  return <p>fix: id {id} → {name}</p>;
}
const root = createRoot(document.getElementById('root'));
const show = id => root.render(<><BugDetail id={id} /><FixDetail id={id} /></>);
show(1);
setTimeout(() => show(2), 100);
setTimeout(() => document.querySelectorAll('p').forEach(p => console.log(p.textContent)), 250);
</script>

সত্যিকারের আউটপুট:

bug: id 2 → জামদানি শাড়ি
fix: id 2 → নকশিকাঁথা

পর্দার সবচেয়ে বিপজ্জনক রূপ: “id 2” লেখা, পাশে id ১-এর নাম। কোনো error নেই, তাই শুধু চোখে ধরা পড়ে। ঠিক রূপে [id], আর সাথে cleanup-এর পতাকা, যাতে দ্রুত বদলের সময় পুরোনো উত্তর না বসে। আরেকটি পথ হলো <Detail key={id} />, যা id বদলালে component-কে নতুন করে শুরু করায়।

বাগ ৩ · State mutation: “যোগ করুন” চাপলে কিছু হয় না, পরে হঠাৎ হয়

লক্ষণ: “ঝুড়িতে ডাল যোগ করলাম, সংখ্যা বদলাল না। পরে অন্য কিছু চাপতেই হঠাৎ ডাল দেখা গেল।”

কারণ খোঁজা: “পরে হঠাৎ” শব্দটিই সূত্র: ডেটা বদলেছে, কিন্তু React জানতে পারেনি। Handler-এ items.push আর একই array দিয়ে setItems. Object.is(পুরোনো, নতুন) সত্য, তাই React render বাদ দেয়।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
const add = {};
function Cart({ kind }) {
  const [items, setItems] = useState(['চাল']);
  add[kind] = item => {
    if (kind === 'bug') { items.push(item); setItems(items); }   // একই array, শুধু ভেতর বদল
    else setItems(prev => [...prev, item]);                       // নতুন array
  };
  return <p data-k={kind}>{items.length}টি: {items.join(', ')}</p>;
}
const root = createRoot(document.getElementById('root'));
const draw = label => root.render(<div title={label}><Cart kind="bug" /><Cart kind="fix" /></div>);
const screen = when => document.querySelectorAll('p').forEach(p => console.log(when, p.dataset.k + ':', p.textContent));
draw('প্রথম');
setTimeout(() => { add.bug('ডাল'); add.fix('ডাল'); }, 50);
setTimeout(() => screen('ডাল যোগের পরে'), 120);
setTimeout(() => draw('অন্য কারণে parent render'), 160);
setTimeout(() => screen('parent render-এর পরে'), 230);
</script>

সত্যিকারের আউটপুট:

ডাল যোগের পরে bug: 1টি: চাল
ডাল যোগের পরে fix: 2টি: চাল, ডাল
parent render-এর পরে bug: 2টি: চাল, ডাল
parent render-এর পরে fix: 2টি: চাল, ডাল

অভিযোগটি হুবহু ফিরে এল। Bug-এর array-এ ডাল ঢুকেছিল, কিন্তু পর্দা বদলায়নি। অন্য কারণে parent render হতেই component আবার চলল, আর বদলে যাওয়া array দেখাল। এই ধরনের বাগ খুঁজে পাওয়া কঠিন, কারণ “পরের” ঘটনাকে দোষী মনে হয়। ঠিক রূপ নতুন array বানায় (immutable update). memo, Context আর React Compiler সবই reference মেলায়, তাই mutation সবখানে ভাঙে।

বাগ ৪ · Index key: পরিমাণ ভুল পণ্যে চলে যায়

লক্ষণ: “চালের পরিমাণ ৩ লিখলাম। ওপরে তেল যোগ হতেই ৩ দেখাচ্ছে তেলের পাশে।”

কারণ খোঁজা: Index key-তে React সারিকে চেনে অবস্থান দিয়ে। ওপরে নতুন সারি এলে পুরোনো প্রথম সারির DOM, আর ভেতরের input-এর লেখা, এখন নতুন প্রথম সারির। React DevTools-এ component-এর key দেখে নিশ্চিত হওয়া যায়।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
function Basket({ items, byIndex }) {
  return (<ul data-k={byIndex ? 'key=index' : 'key=id'}>
    {items.map((it, i) => (
      <li key={byIndex ? i : it.id}>{it.name} <input defaultValue="1" size="2" aria-label={it.name + '-এর পরিমাণ'} /></li>
    ))}
  </ul>);
}
let items = [{ id: 'c1', name: 'চাল' }, { id: 'd1', name: 'ডাল' }];
const root = createRoot(document.getElementById('root'));
const draw = () => root.render(<><Basket items={items} byIndex /><Basket items={items} /></>);
const report = () => document.querySelectorAll('ul').forEach(ul => console.log(ul.dataset.k + ' →',
  [...ul.children].map(li => li.firstChild.textContent + '=' + li.querySelector('input').value).join(', ')));
draw();
setTimeout(() => {
  document.querySelectorAll('ul').forEach(ul => { ul.querySelector('input').value = '3'; });  // চাল 3 কেজি
  items = [{ id: 't1', name: 'তেল' }, ...items];                                              // ওপরে নতুন সারি
  draw();
  setTimeout(report, 50);
}, 50);
</script>

সত্যিকারের আউটপুট:

key=index → তেল=3, চাল=1, ডাল=1
key=id → তেল=1, চাল=3, ডাল=1

Index key-তে 0 নম্বর <li> আগে চাল ছিল, এখন তেল; তার input আর লেখা “3” একই জায়গায় রয়ে গেল। Id key-তে React বুঝল তেল নতুন, তাই নতুন সারি বানাল, আর চালের input চালের সাথেই সরল। Uncontrolled input, local state, focus আর animation সবই এভাবে ভুল সারিতে চলে যায়। Dev build-এর key warning এখানে আসত না, কারণ key দেওয়া আছে; ভুল key-এর জন্য কোনো warning নেই।

বাগ ৫ · Infinite effect loop: Network ট্যাবে অবিরাম অনুরোধ

লক্ষণ: “পণ্যের পাতা খুললে ল্যাপটপের পাখা জোরে ঘোরে। Network ট্যাবে একই /api/products বারবার।”

কারণ খোঁজা: Effect-এ console.count('effect') দিলে সংখ্যা থামে না। Dependency-তে opts, যা প্রতি render-এ নতুন object. চক্র: render → নতুন opts → effect → fetch → setItems(নতুন array) → render. নিচে শিক্ষণের জন্য নকল API ছয়টি উত্তরের পরে আর সাড়া দেয় না, যাতে চক্র থামে।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
let calls = 0;
function api(opts) {                     // শিক্ষণের জন্য: ৬টি উত্তরের পরে আর সাড়া দেয় না
  calls++;
  return calls > 6 ? new Promise(() => {}) : Promise.resolve(['শাড়ি', 'শাল']);
}
function BugList({ category }) {
  const [items, setItems] = useState([]);
  const opts = { category, limit: 20 };          // প্রতি render-এ নতুন object
  useEffect(() => { api(opts).then(setItems); }, [opts]);
  return <p>bug: {items.length}টি</p>;
}
function FixList({ category }) {
  const [items, setItems] = useState([]);
  useEffect(() => {
    let live = true;
    api({ category, limit: 20 }).then(r => { if (live) setItems(r); });
    return () => { live = false; };
  }, [category]);                                // শুধু primitive মান
  return <p>fix: {items.length}টি</p>;
}
const root = createRoot(document.getElementById('root'));
root.render(<FixList category="পোশাক" />);
setTimeout(() => { console.log('fix: API ডাক =', calls); calls = 0; root.render(<BugList category="পোশাক" />); }, 100);
setTimeout(() => { console.log('bug: API ডাক =', calls); root.unmount(); }, 300);
</script>

সত্যিকারের আউটপুট:

fix: API ডাক = 1
bug: API ডাক = 7

ঠিক রূপে একটি ডাক। বাগওয়ালা রূপে ছয়টি উত্তর আর সপ্তম ডাক, যার উত্তর আসেনি বলে চক্র থেমেছে। আসল server থাকলে এটি থামত না, আর rate limit বা বিলের আকারে টের পাওয়া যেত। উত্তরটি একই দেখালেও প্রতিবার নতুন array, তাই setItems সবসময় render ঘটায়। সমাধান: object effect-এর ভেতরে বানানো, আর শুধু primitive category dependency-তে। setState সরাসরি effect-এ, প্রতিবার নতুন মান দিয়ে চালালে React শেষে Maximum update depth exceeded ছুড়ে থামায়; async চক্রে সেই সুরক্ষাও নেই।

বাগ ৬ · Hydration mismatch: অর্ডারের সময় ঝলক দিয়ে বদলায়

লক্ষণ: “অর্ডারের পাতায় সময় প্রথমে এক রকম, এক মুহূর্ত পরে আরেক রকম। Dev console-এ লাল ‘Hydration failed’.”

কারণ খোঁজা: বার্তার diff পড়ুন। পর্ব ৫৬-এর সত্যিকারের run-এ (Next.js 16.4.4) server TZ=UTC-এ আর browser ঢাকায় ছিল:

Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client.
  …
+ ৩/১১/২০২৬, ২:৩০:০০ AM
- ২/১১/২০২৬, ৮:৩০:০০ PM

+ হলো client যা বানাল, - হলো server-এর HTML. এই পাতার live ব্লকে hydrateRoot নেই, তাই নিচে একটি শিক্ষণ মডেল: একই component দুটি “যন্ত্রে” আলাদা time zone-এ আসল React দিয়ে render করে লেখা মেলানো হয়, যেমন hydration করে।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
// শিক্ষণ মডেল: একই component দুই "যন্ত্রে" render করে লেখা মেলানো।
// আসল hydration-এ server-এর HTML আর ব্রাউজারের প্রথম render এভাবেই মেলানো হয়।
const placedAt = Date.UTC(2026, 10, 2, 20, 30);      // 2026-11-02T20:30Z
function BugTime({ machineTz }) {                     // যে যন্ত্রে চলছে তার time zone নেয়
  return <time>{new Date(placedAt).toLocaleString('bn-BD', { timeZone: machineTz })}</time>;
}
function FixTime() {                                  // time zone স্পষ্ট লেখা, দুই দিকে এক
  return <time>{new Date(placedAt).toLocaleString('bn-BD', { timeZone: 'Asia/Dhaka' })}</time>;
}
function html(el) {
  const box = document.createElement('div');
  ReactDOM.flushSync(() => createRoot(box).render(el));
  return box.textContent;
}
function check(name, server, client) {
  console.log(name, server === client ? 'মিলেছে' : 'মেলেনি');
  console.log('   server:', server, '│ client:', client);
}
check('bug →', html(<BugTime machineTz="UTC" />), html(<BugTime machineTz="Asia/Dhaka" />));
check('fix →', html(<FixTime />), html(<FixTime />));
</script>

সত্যিকারের আউটপুট:

bug → মেলেনি
   server: ২/১১/২০২৬, ৮:৩০:০০ PM │ client: ৩/১১/২০২৬, ২:৩০:০০ AM
fix → মিলেছে
   server: ৩/১১/২০২৬, ২:৩০:০০ AM │ client: ৩/১১/২০২৬, ২:৩০:০০ AM

মডেলের দুটি লেখা পর্ব ৫৬-এর আসল diff-এর সাথে হুবহু মিলেছে। একই মুহূর্ত UTC-তে ২ নভেম্বর রাত সাড়ে আটটা, ঢাকায় ৩ নভেম্বর রাত আড়াইটা; তারিখই বদলে যায়। সমাধান: time zone স্পষ্ট লেখা, অথবা ক্রেতার নিজের সময় দেখাতে চাইলে mount-এর পরে effect-এ দেখানো, আর প্রথম render-এ একটি স্থির লেখা। এক element-এর অনিবার্য সময়-লেখায় suppressHydrationWarning চলে, অন্য কোথাও নয়।

প্র ৩৮ফলো-আপ: production-এ শুধু “Minified React error #418” এল, কোনো diff নেই। এখন? (কঠিন)

উত্তর: Production React লম্বা বার্তা ফেলে দেয়, তাই react.dev-এর error পাতায় #418 দেখি (এটি hydration না মেলার error; পর্ব ৫৬-এর run-এ এর সাথে args[]=text এসেছিল)। তারপর একই পাতা dev build-এ খুলে diff দেখি, server আর ক্রেতার time zone, locale আর extension মিলিয়ে। Error tracking-এ ব্রাউজার, locale আর পাতার URL ধরে রাখলে কার কাছে হচ্ছে তা জানা যায় (পর্ব ৮৩)। ঠিক করার পরে Playwright test-এ timezoneId আর locale বদলে একই পাতা চালিয়ে console-এ error নেই, তা যাচাই করি (পর্ব ৭৬)।

প্র ৩৯ছয়টি বাগের পরে: কোনগুলো যন্ত্রে ধরা যেত? (মাঝারি)

উত্তর: বাগ ১ আর ২: react-hooks/exhaustive-deps. বাগ ৫: একই নিয়ম object dependency নিয়ে সতর্ক করে, আর React Compiler-এর lint-ও render-এর গোলমাল ধরে। বাগ ৩: render-এর বাইরে mutation সব ক্ষেত্রে lint ধরে না, তবে TypeScript-এ readonly array বা Immer সাহায্য করে। বাগ ৪: কোনো টুল জানে না কোন key “ঠিক”; একটি component test, যা ওপরে সারি যোগ করে মান যাচাই করে, ধরতে পারে। বাগ ৬: dev build-এর console আর বিভিন্ন timezoneId-এ E2E test. ইন্টারভিউতে এই উত্তর দেখায় আপনি বাগ ঠিক করেই থামেন না, ফিরে আসা আটকান।

৮ · Frontend system design — বাজারঘরের পণ্য তালিকার পাতা

রিভিশন: পর্ব ৫৫, পর্ব ৫৩, পর্ব ৫৮, পর্ব ৬৪, পর্ব ৮৪, CDN আর edge

প্রশ্ন: “বাজারঘরের ‘শাড়ি’ বিভাগের তালিকার পাতা নকশা করুন। Filter, sort, পাতা ভাগ, আর ‘ঝুড়িতে যোগ’ থাকবে। বেশিরভাগ ক্রেতা মোবাইলে।” পঁয়তাল্লিশ মিনিটের মতো সময় ধরে নিচে একটি মডেল উত্তর। Backend-এর বড় নকশা (database, queue, scale) পর্ব ৯৩-এর বিষয়; এখানে browser থেকে API পর্যন্ত।

ধাপ ১ · চাহিদা আর ধরে নেওয়া সংখ্যা

প্রথমে জোরে বলুন কী ধরে নিচ্ছেন, আর ইন্টারভিউয়ারকে মিলিয়ে নিতে বলুন। সংখ্যাগুলো ধরে নেওয়া, কোনো বাস্তব সাইটের নয়:

  • বিভাগে প্রায় ২০,০০০ পণ্য, প্রতি পাতায় ২৪টি কার্ড। Filter: রং, দামের সীমা, কাপড়। Sort: দাম, নতুন।
  • দাম আর স্টক দিনে কয়েকবার বদলায়; পণ্যের নাম আর ছবি কম।
  • বিভাগের পাতা search engine-এ আসা দরকার; লগইন করা আর না-করা দুই রকম ক্রেতা।
  • অ-কার্যগত চাহিদা: ধীর মোবাইল নেটওয়ার্ক, কম দামের Android ফোন, screen reader আর keyboard-এ চলা।

বাজারঘরের তালিকার পাতা: কোথায় কী চলে

Server-rendered grid, small client islands, cached HTML and data, image CDN

বাজারঘরের পণ্য তালিকার পাতার স্তর ফোনের browser /category/saree?sort=price&page=2 CDN / edge বেনামি HTML: s-maxage stale-while-revalidate Image CDN AVIF, WebP srcset মাপে Next.js server ১ · static shell আগে ২ · ProductGrid stream (server component) data cache: tag products searchParams যাচাই Client island FilterForm AddToCart ঝুড়ির সংখ্যা (ব্যক্তিগত, public cache নয়) API আর database শুধু id, নাম, দাম, ছবি, স্টক · index (category, price, id) Budget: LCP ≤ 2.5 s · INP ≤ 200 ms · CLS ≤ 0.1 · JS ≤ 200 KB
ছবি ৯২.২ — পাতার বেশিরভাগ server-এ, আর JavaScript যায় শুধু দুটি ছোট island-এ। ব্যক্তিগত তথ্য public cache-এর বাইরে। নিচের লাল দাগটি পুরো নকশার মাপকাঠি।

ধাপ ২ · Rendering-এর সিদ্ধান্ত

পাতাটি App Router-এ একটি server component. Header, বিভাগের নাম আর filter-এর কাঠামো একটি static shell; পণ্যের grid Suspense-এর ভেতরে stream হয়, যাতে ধীর query পুরো পাতা আটকে না রাখে (পর্ব ৫৫)। Filter, sort আর পাতার নম্বর থাকে URL-এর search params-এ, তাই link শেয়ার, back বোতাম আর search engine সবাই একই অবস্থা দেখে। প্রতিটি সম্ভাব্য combination আগে build করা অসম্ভব, তাই HTML নয়, ডেটার অনুরোধ cache হবে। Client component মাত্র দুটি: FilterForm আর AddToCart. Filter-কে সাধারণ GET form হিসেবে লিখলে JavaScript আসার আগেও কাজ করে, এটিই progressive enhancement.

ধাপ ৩ · Data fetching আর cache

  • যা আনব: কার্ডে যা দেখায় শুধু তা: id, নাম, দাম, ছোট ছবির ঠিকানা, স্টক আছে কি না। পুরো বিবরণ পণ্যের পাতায়। এক query-তে, N+1 ছাড়া (পর্ব ৪২)।
  • পাতা ভাগ: পাতার নম্বর দেখাতে offset সহজ, আর ২৪টির পাতায় প্রথম কয়েক পাতা সস্তা। “আরও দেখুন” ধরনের অবিরাম scroll হলে cursor, কারণ গভীর offset ধীর আর মাঝে পণ্য ঢুকলে সারি লাফায় (পর্ব ৩৪)।
  • Cache-এর স্তর: বেনামি ক্রেতার HTML-এ CDN-এ অল্প s-maxage আর stale-while-revalidate; server-এর data cache-এ products tag, দাম বদলালে admin-এর action থেকে সেই tag revalidate (পর্ব ৫৩); hash নামের JS আর CSS-এ এক বছর immutable. ঝুড়ির সংখ্যা আর লগইন নাম আলাদা অনুরোধে, private; নইলে একজনের ঝুড়ি আরেকজন দেখতে পারেন।
  • Cache key: search params সাজিয়ে, অচেনা আর tracking param বাদ দিয়ে, যাতে ?page=2&sort=price আর ?sort=price&page=2 একই cache পায়। Param যাচাই Zod-এ: page ১ থেকে সীমার মধ্যে, sort একটি তালিকা থেকে।

ধাপ ৪ · ছবি — হিসাবসহ

তালিকার পাতায় সবচেয়ে বড় বোঝা ছবি। ধরে নিই প্রতিটি thumbnail AVIF বা WebP-তে প্রায় ২৫ KB. ২৪টি একসাথে নামালে ২৪ × ২৫ = ৬০০ KB. পর্ব ৮৪-এর মতো ১.৬৩৮৪ Mbps-এর ধীর সংযোগে ৬,০০,০০০ × ৮ ÷ ১৬,৩৮,৪০০ ≈ ২.৯৩ সেকেন্ড, শুধু ছবিতেই LCP-র পুরো বাজেট পার। কিন্তু ফোনের প্রথম পর্দায় দেখা যায় প্রায় ৪টি কার্ড। সেগুলো আগে, বাকিগুলো loading="lazy": ৪ × ২৫ = ১০০ KB, প্রায় ১,০০,০০০ × ৮ ÷ ১৬,৩৮,৪০০ ≈ ০.৪৯ সেকেন্ড, আর ১৫০ KB-র ছবির budget-এর ভেতরে। প্রথম ছবিতে fetchpriority="high", কারণ সেটিই সম্ভবত LCP element. সব ছবিতে width আর height বা aspect-ratio, যাতে CLS না হয়; srcset আর sizes, যাতে ফোন ছোট ছবি পায় (পর্ব ৬৪)।

ধাপ ৫ · Accessibility

  • তালিকা আসল <ul>, প্রতিটি কার্ডে পণ্যের নামের heading আর link; দাম লেখায়, শুধু রঙে নয়।
  • Filter <fieldset> আর <legend>-এ, প্রতিটি checkbox-এ label. Filter বদলে ফল এলে “৩১২টি শাড়ি পাওয়া গেছে” একটি aria-live অংশে ঘোষণা।
  • পাতা ভাগের link <nav aria-label="পাতা">-এ, বর্তমান পাতায় aria-current="page". “ঝুড়িতে যোগ” বোতামের নামে পণ্যের নাম, যাতে ২৪টি একই নামের বোতাম না হয়।
  • Keyboard-এ সব চলে, focus রেখা দৃশ্যমান, রঙের contrast WCAG-এর সীমায় (HTML ও CSS কোর্স)। যাচাই হবে Playwright-এর সাথে axe-core দিয়ে (পর্ব ৭৬)।

ধাপ ৬ · Performance budget, মাপা আর ঝুঁকি

মাপসীমা (ধরে নেওয়া)কোথায় মাপাভাঙলে প্রথম সন্দেহ
LCP≤ ২.৫ s, p75 মোবাইলField: web-vitals; lab: Lighthouseপ্রথম ছবি lazy, বড় ছবি, ধীর TTFB
INP≤ ২০০ msField dataFilter-এ পুরো grid-এর client render, ভারী handler
CLS≤ ০.১Field আর labমাপহীন ছবি, দেরিতে আসা ব্যানার, font
JavaScript≤ ২০০ KB সংকুচিতBuild-এর treemap, Lighthouse CIGrid ভুল করে client component, ভারী library
প্রথম পর্দার ছবি≤ ১৫০ KBLighthouse CI-এর resource summaryLazy নয় এমন অনেক ছবি, ভুল sizes

শেষে ঝুঁকি আর trade-off নিজে বলুন। Filter-এর অসংখ্য combination search engine-এ duplicate পাতা বানাতে পারে, তাই canonical আর কোন combination index হবে তার নিয়ম (পর্ব ৫৮)। CDN-এর অল্প s-maxage মানে দাম বদলের পরে কয়েক সেকেন্ড পুরোনো দাম দেখা যেতে পারে, তাই checkout-এ দাম আবার server থেকে যাচাই। Recommendation-এর মতো ঐচ্ছিক অংশ ভাঙলে পুরো পাতা যেন না ভাঙে, তাই সেখানে আলাদা error boundary আর Suspense. আর চালু করার পরে field data দেখে budget-এর সাথে মিলিয়ে দেখা।

প্র ৪০ফলো-আপ: “পুরো তালিকা client-এ fetch করে SPA বানালে কী ক্ষতি?” (মাঝারি)

উত্তর: প্রথম HTML-এ পণ্য থাকে না, তাই আগে JS নামে, তারপর চলে, তারপর API ডাক, তারপর ছবি: পরপর চারটি অপেক্ষা, ধীর ফোনে LCP অনেক দেরিতে। Search engine-এর জন্যও কাজ বেশি। JS bundle-এ grid-এর পুরো কোড যায়। লাভ হলো পাতা বদলে দ্রুত অনুভূতি, যা App Router-এর client navigation server rendering রেখেও দেয়। লগইন করা ক্রেতার ব্যক্তিগত dashboard-এ CSR ঠিক আছে; public তালিকায় নয়।

প্র ৪১ফলো-আপ: Flash sale-এ স্টক প্রতি সেকেন্ডে বদলায়। নকশার কী বদলাবেন? (কঠিন)

উত্তর: পাতার বাকিটা cache-এ রেখে শুধু স্টকের ছোট অংশ dynamic: কার্ডে “স্টক আছে/শেষ”-এর জন্য আলাদা ছোট অনুরোধ বা stream, অথবা SSE বা polling দিয়ে শুধু ঐ বিক্রির পণ্যগুলোর সংখ্যা (পর্ব ৬১)। পর্দার সংখ্যা শুধু তথ্য; আসল সিদ্ধান্ত হয় order-এর সময় server-এর transaction-এ, যাতে বেশি বিক্রি না হয় (পর্ব ৪৩)। Cache-এর ব্যর্থতায় database-এ একসাথে ভিড় ঠেকাতে stale-while-revalidate আর single-flight-এর মতো নিয়ম, যা পর্ব ৯৩-এর backend আলোচনার সাথে জোড়ে।

৯ · ইন্টারভিউয়ারকে প্রশ্ন, প্রস্তুতির চেকলিস্ট আর শেষ সাত দিন

রিভিশন: পর্ব ৭২, পর্ব ৮৬, পর্ব ৮৮, Software engineering ইন্টারভিউ

ইন্টারভিউয়ারকে আপনার প্রশ্ন

শেষের “আপনার কোনো প্রশ্ন আছে?” আসলে আরেকটি সুযোগ। ভালো প্রশ্ন দেখায় আপনি কাজটি সত্যিই বুঝতে চান। বেতনের মতো বিষয় সাধারণত HR-এর আলাপে আসে।

  • Frontend-এ এখন কোন stack, আর কোন অংশটি দল সবচেয়ে বেশি বদলাতে চায়?
  • Component test আর E2E test কে লেখেন, আর CI-তে কী চলে?
  • Design system বা component library আছে কি, আর তার মালিক কে?
  • Performance আর accessibility কীভাবে মাপা হয়: field data, budget, নাকি অভিযোগ এলে?
  • Code review কেমন হয়, আর একজন নতুন সদস্যের প্রথম ৯০ দিনে কী প্রত্যাশা?
  • AI coding সহকারী ব্যবহারের নিয়ম কী, আর তার কোড কীভাবে review হয়?

প্রস্তুতির চেকলিস্ট

মডিউল ধরে দ্রুত রিভিশন

মডিউলনিজেকে জিজ্ঞেস করুনপর্ব
১ · মানচিত্রCSR আর SSR-এ প্রথম HTML-এ কী থাকে?৩, ৬
২ · React-এর ভিত্তিRender আর commit-এর পার্থক্য, key-এর আসল কাজ?১০, ১৩, ১৫
৩ · বড় React অ্যাপServer state কেন Context-এ নয়?১৬, ১৯, ২৩
৭ · নিরাপত্তাFrontend-এ button লুকানো কেন অনুমতি নয়?৪৮, ৫০
৮ · Next.jsHydration mismatch-এর তিনটি কারণ?৫২, ৫৫, ৫৬
১০ · অন্য frameworkVue-র reactivity আর React-এর re-render-এর মূল পার্থক্য?৬৬, ৬৭
১১ · Testingকোন query আগে, আর কেন test id শেষে?৭৪, ৭৬
১২ · ProductionLCP-র সীমা আর তা ভাঙার প্রথম সন্দেহ?৭৯, ৮৪

শেষ সাত দিনের পরিকল্পনা

দিনে ঘণ্টা দুয়েক ধরে সাজানো। নিজের সময় অনুযায়ী বদলে নিন।

দিনকাজশেষে যা পারবেন
১অংশ ২: render, key, stateSnapshot-এর প্রশ্নে আউটপুট আগেই বলা
২অংশ ৩: effect, memo, ContextDependency array-র নিয়ম কারণসহ
৩অংশ ৪ ও ৫: Next.js, form, a11y, CWV, testServer আর client-এর সীমানা আঁকা
৪অংশ ৬: সমস্যা ১ ও ২ নিজে লেখাDebounce আর তিন অবস্থার তালিকা
৫অংশ ৬: সমস্যা ৩ ও ৪ নিজে লেখাযাচাইসহ form আর বাতিলযোগ্য fetch
৬অংশ ৭: ছয়টি বাগ, লক্ষণ থেকেপ্রতিটির প্রমাণের পথ আর test
৭অংশ ৮: বন্ধুর সাথে নকল system design৪৫ মিনিটে ছয় ধাপ, সংখ্যাসহ

আগের দিন সন্ধ্যায় নতুন কিছু শিখবেন না। নিজের প্রকল্পটি একবার খুলে চালিয়ে দেখুন, কারণ প্রশ্ন সেখান থেকেও আসতে পারে।

AI-এর যুগে frontend ইন্টারভিউর প্রস্তুতি

AI একজন ধৈর্যশীল নকল ইন্টারভিউয়ার হতে পারে। তবে সে প্রায়ই পুরোনো API শেখায়: class component-এর lifecycle, Next.js-এর পুরোনো pages router-এর getServerSideProps, অথবা React 19-এ সরানো ReactDOM.render. সে নেই এমন hook বা package বানায়, effect-এ dependency মুছে “ঠিক” করে দেয়, আর ভুল চেপে রাখতে খালি catch লেখে। Secret বা অফিসের কোড prompt-এ দিলে ফাঁস হতে পারে। আর live ইন্টারভিউতে অনুমতি ছাড়া AI ব্যবহার করবেন না।

AI-কে উত্তর লিখতে না বলে আপনাকে চেপে ধরতে বলুন। নিজের কোড চালিয়ে মিলিয়ে নিন।

তুমি একজন কড়া frontend ইন্টারভিউয়ার। আমার স্তর: [শুরু/মাঝারি/উন্নত]। Stack: React 19, Next.js App Router, TypeScript. ১. আমাকে একটি React live coding সমস্যা দাও (৩০ লাইনের মধ্যে করা যায় এমন)। শুধু চাহিদা বলো, সমাধান বলো না। ২. আমি কোড দিলে আগে জিজ্ঞেস করো: loading, error, empty আর keyboard-এ কী হবে? ৩. তারপর একটি বাগওয়ালা component দাও আর শুধু লক্ষণ বলো। আমি প্রমাণ ছাড়া অনুমান করলে থামিয়ে জিজ্ঞেস করো, "কীভাবে নিশ্চিত হবে?" ৪. কোনো API নিয়ে নিশ্চিত না হলে "নিশ্চিত নই" লেখো, আর react.dev বা nextjs.org-এর কোন পাতা দেখব তা বলো। ৫. শেষে পাঁচটি ক্ষেত্রে ০ থেকে ২ নম্বর দাও: চাহিদা মেলানো, সঠিকতা, edge case, ব্যাখ্যা, test-এর ভাবনা।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি React বাগের অভিযোগ এলে ভালো কাজের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. অভিযোগের লক্ষণ মিলিয়ে বাগটি আবার ঘটানো
  2. সবচেয়ে ছোট কোডে বাগটি আলাদা করা
  3. একটি অনুমান বলে log বা DevTools দিয়ে প্রমাণ করা
  4. কারণ ধরে কোড ঠিক করা
  5. একটি test বা lint নিয়ম দিয়ে ফিরে আসা আটকানো

(সহজ) const [n, setN] = useState(0). একটি click handler-এ পরপর তিনবার setN(n + 1) লেখা। একবার click-এর পরে পর্দায় n কত?

  • 3
  • 1
  • 0
  • 2

(মাঝারি) একটি তালিকায় key={index}, প্রতিটি সারিতে একটি uncontrolled input. প্রথম সারিতে “3” লেখার পরে তালিকার ওপরে নতুন একটি পণ্য যোগ হলো। “3” কোথায় দেখা যাবে?

  • আগের প্রথম পণ্যের পাশে, এখন দ্বিতীয় সারিতে
  • কোথাও না, সব input নতুন করে শুরু হবে
  • নতুন যোগ হওয়া পণ্যের পাশে, প্রথম সারিতে
  • React error দেখাবে আর তালিকা আঁকবে না

(মাঝারি) Provider লিখেছে <UserContext value={{ user }}>. Provider-এর component অন্য কারণে render হলো, user বদলায়নি। কী ঘটবে?

  • সব consumer আবার render হবে, কারণ value নতুন object
  • কোনো consumer render হবে না, কারণ ভেতরের user একই
  • শুধু memo ছাড়া consumer-গুলো render হবে
  • React একটি error দেখাবে

(মাঝারি) একটি component-এ const opts = { page: 1 } লেখা, আর useEffect(() => { api(opts).then(setItems) }, [opts]). api প্রতিবার নতুন array ফেরত দেয়। কী হবে?

  • Effect একবার চলবে, কারণ page সবসময় 1
  • দুবার চলে থেমে যাবে, কারণ দ্বিতীয় উত্তর একই
  • Build-এর সময় error, তাই কোড চলবেই না
  • অবিরাম চক্র: প্রতিটি উত্তরে নতুন render আর নতুন অনুরোধ

(সহজ) Next.js App Router-এ 'use client' ছাড়া একটি component ফাইল। নিচের কোনটি সেখানে সরাসরি করা যায়?

  • useState দিয়ে ঝুড়ির সংখ্যা রাখা
  • Database থেকে await করে পণ্যের তালিকা পড়া
  • onClick দিয়ে বোতামের কাজ
  • useEffect-এ localStorage পড়া

(মাঝারি) Server TZ=UTC-এ, ক্রেতা ঢাকায়। একটি client component লেখে new Date(t).toLocaleString('bn-BD'). Dev console-এ “Hydration failed”. সবচেয়ে ভালো সমাধান কোনটি?

  • timeZone: 'Asia/Dhaka' স্পষ্ট লিখে দুই দিকে একই ফরম্যাট
  • পুরো পাতায় SSR বন্ধ করে দেওয়া
  • StrictMode বন্ধ করা
  • Locale বদলে en-US করা

(সহজ) Core Web Vitals-এ INP-এর “ভালো” সীমা কত, আর কোন মাপে বিচার?

  • ২.৫ সেকেন্ড, গড়ে
  • ০.১, সবচেয়ে খারাপ visit-এ
  • ≤ ২০০ ms, আসল ব্যবহারকারীর 75th percentile-এ
  • ≤ ১০০ ms, Lighthouse-এর lab run-এ

(সহজ) Testing Library-তে “অর্ডার করুন” লেখা বোতাম খুঁজতে সবচেয়ে ভালো query কোনটি?

  • getByTestId('order-btn')
  • container.querySelector('.btn-primary')
  • getByText('অর্ডার করুন')
  • getByRole('button', { name: 'অর্ডার করুন' })

(মাঝারি) ৩০০ ms-এর debounce. ব্যবহারকারী ০, ১০০ আর ২০০ ms-এ তিনটি অক্ষর লিখে থামলেন। API কতবার আর কখন ডাকা হবে?

  • তিনবার, ৩০০, ৪০০ আর ৫০০ ms-এ
  • একবার, ৫০০ ms-এ
  • একবার, ৩০০ ms-এ
  • একবারও না

(মাঝারি) একটি fetch effect-এর cleanup-এ controller.abort() লেখা। এর মূল উদ্দেশ্য কী?

  • ব্যর্থ অনুরোধ স্বয়ংক্রিয়ভাবে আবার পাঠানো
  • উত্তর browser cache-এ রাখা
  • পুরোনো অনুরোধ থামিয়ে দেরিতে আসা ভুল উত্তর ঠেকানো
  • Server-এর সব কাজ নিশ্চিতভাবে বাতিল করা

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব শব্দ এখানে একসাথে:

মনে রাখুন

  1. উত্তরের ছক: নিয়ম, তার কারণ, একটি উদাহরণ, আর কীভাবে যাচাই করা যায়। দুর্বল উত্তর শুধু সংজ্ঞা দেয়।
  2. Render মানে হিসাব, DOM বদল নয়। State snapshot, updater আর batching বুঝলে আউটপুট-অনুমানের প্রশ্ন সহজ। Key হলো পরিচয়।
  3. Effect বাইরের system-এর সাথে sync রাখার জন্য। Dependency কোড ঠিক করে, cleanup পুরোনো কাজ থামায়, আর মুছে দেওয়া dependency-ই বেশিরভাগ বাগের উৎস।
  4. Live coding-এ আগে প্রশ্ন, তারপর সরল রূপ, তারপর edge case, আর জোরে ভাবা। ডিবাগিংয়ে লক্ষণ থেকে প্রমাণ, তারপর সমাধান, শেষে test.
  5. নকশার প্রশ্নে সংখ্যা আর trade-off: কী server-এ, কী client-এ, কী cache-এ, কোন ছবি আগে, আর কোন budget ভাঙলে কী সন্দেহ।

পরের পর্বে: পর্ব ৯৩-এ ফুল-স্ট্যাক ইন্টারভিউ: API, database, নকশা আর আচরণগত প্রশ্ন।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান