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

JSX — markup আর logic একসাথে, render কীভাবে হয়

JSX and How React Renders — Compilation, Elements, Trigger, Render and Commit

Taught in Bangla, this lesson shows that JSX compiles to jsx() calls the browser never sees, curly-brace expressions, attributes such as className and htmlFor, children and Fragments with TSX types, React elements as plain objects, which values render and the classic 0 with && bug, automatic escaping and the risk of dangerouslySetInnerHTML, the trigger, render and commit steps, and showing Bangla text and numbers with Intl.

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

  • JSX compile হয়ে কী হয়, আসল esbuild আর TypeScript-এর আউটপুট দেখে বোঝা
  • {}-এর ভেতরে expression চলে কিন্তু statement চলে না কেন
  • className, htmlFor, style object, camelCase event, children আর Fragment
  • কোন মান পর্দায় আসে, কোনটি আসে না, আর 0 && বাগ ধরা ও সারানো
  • Escaping কীভাবে XSS ঠেকায়, আর dangerouslySetInnerHTML কখন ঝুঁকি
  • Trigger, render, commit: কোনটির পর কোনটি হয়, আর re-render ঠিক কখন হয়
  • বাংলায় সংখ্যা ও তারিখ Intl দিয়ে দেখানো, আর TSX-এ children-এর type

১ · JSX আসলে কী: HTML-এর মতো দেখতে JavaScript

পর্ব ০৭-এ দেখেছেন, React-এ UI ভাগ হয় component-এ, আর component হলো একটি ফাংশন। সেই ফাংশন কী ফেরত দেয়? দেখে মনে হয় HTML. কিন্তু একটি .js ফাইলের ভেতরে HTML তো চলার কথা নয়। এই ধাঁধার উত্তর হলো JSX.

রেস্তোরাঁর ওয়েটার খাতায় লেখেন “২ কাচ্চি, ১ বোরহানি, ঝাল কম”। রান্নাঘরের জন্য এই সংক্ষেপটুকুই যথেষ্ট, কারণ মাঝখানে কেউ একজন সেটি পুরো অর্ডারে বদলে নেন। JSX হলো সেই সংক্ষেপ। আপনি লেখেন সহজে পড়া যায় এমন রূপ, আর একটি টুল সেটিকে পুরো JavaScript ডাকে বদলে দেয়। Browser পায় শুধু সেই বদলানো রূপ।

বাজারঘরের পণ্যের একটি কার্ড দিয়ে শুরু করি। এটি একটি সাধারণ .jsx ফাইল:

export function ProductCard({ name, taka, stock }) {
  return (
    <div className="card">
      <h3>{name}</h3>
      <p style={{ color: 'green' }}>৳{taka}</p>
      {stock > 0 && <button onClick={addToCart}>কার্টে রাখুন</button>}
    </div>
  );
}

এবার এটিকে আসল compiler দিয়ে অনুবাদ করি। esbuild একটি দ্রুত JavaScript build টুল। নিচের লেখাটি এই পর্বের জন্য নিজের মেশিনে চালানো সত্যিকারের run (esbuild 0.28.2, React 19.3.1, লেখার সময়ের version):

~/bazarghar$ npx esbuild src/ProductCard.jsx --jsx=automatic --charset=utf8
"use strict";
import { jsx, jsxs } from "react/jsx-runtime";
export function ProductCard({ name, taka, stock }) {
  return /* @__PURE__ */ jsxs("div", { className: "card", children: [
    /* @__PURE__ */ jsx("h3", { children: name }),
    /* @__PURE__ */ jsxs("p", { style: { color: "green" }, children: [
      "৳",
      taka
    ] }),
    stock > 0 && /* @__PURE__ */ jsx("button", { onClick: addToCart, children: "কার্টে রাখুন" })
  ] });
}

এখানে কয়েকটি জিনিস খেয়াল করার মতো:

  • প্রতিটি tag একটি ফাংশন ডাক হয়ে গেছে। প্রথম argument হলো tag-এর নাম, দ্বিতীয়টি হলো attribute-গুলোর একটি object.
  • ভেতরের জিনিসগুলো children নামের একটি ঘরে চলে গেছে। একটি সন্তান থাকলে jsx, একাধিক হলে jsxs (শেষে s) ডাকা হয়, আর children হয় array.
  • Import নিজে থেকেই বসেছে। আপনি react/jsx-runtime লেখেননি। এই ব্যবস্থার নাম automatic JSX runtime.
  • stock > 0 && হুবহু রয়ে গেছে। {}-এর ভেতরের JavaScript অনুবাদ হয় না, সোজা কপি হয়।
  • /* @__PURE__ */ একটি মন্তব্য মাত্র। এটি bundler-কে জানায় ডাকটির ফল ব্যবহার না হলে সেটি মুছে ফেলা নিরাপদ।

TypeScript compiler-ও একই কাজ করে (--jsx react-jsx, TypeScript 7.0.2, সত্যিকারের run). শুধু নাম সামান্য আলাদা, আর বাংলা অক্ষর \u escape হয়ে যায়; string-এর মান একই থাকে:

import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
export function ProductCard({ name, taka, stock }) {
    return (_jsxs("div", { className: "card", children: [_jsx("h3", { children: name }), _jsxs("p", { style: { color: 'green' }, children: ["৳", taka] }), …

আর পুরোনো “classic” পদ্ধতিতে (--jsx=transform) একই কোড হতো React.createElement("div", { className: "card" }, …). তখন প্রতিটি ফাইলের ওপরে import React from 'react' লিখতে হতো। AI বা পুরোনো টিউটোরিয়ালের কোডে আজও সেই লাইনটি দেখবেন।

React ২০১৩ সালে Facebook থেকে open source হয়, আর JSX তখন থেকেই তার সঙ্গী। JSX-এর ধারণায় PHP-র জন্য বানানো Facebook-এর XHP-র প্রভাব ছিল বলে React দলের লোকজন উল্লেখ করেছেন। JSX কোনো browser-এর ভাষা নয়, ECMAScript মানদণ্ডের অংশও নয়; এটি একটি আলাদা syntax, যা compiler অনুবাদ করে। ২০২০ সালের সেপ্টেম্বরে React দল নতুন JSX transform ঘোষণা করে (React 17 RC থেকে, পরে পুরোনো কয়েকটি শাখাতেও), যার ফলে JSX লিখতে আর React import করতে হয় না।

Vite-এ এই কাজ আপনি হাতে করেন না। পর্ব ০৫-এর কাজের পরিবেশে যে Vite বসিয়েছেন, সেটিই ফাইল পরিবেশনের সময় এই অনুবাদ চালায়। ভেতরের টুল version ভেদে বদলাতে পারে, কিন্তু ফলের রূপ একই: react/jsx-runtime-এর ডাক। এই পর্বের live editor-এও একই ঘটনা ঘটে, শুধু অনুবাদটি হয় আপনার browser-এর ভেতরে।

মূল ধারণা

JSX মানে ফাংশন ডাক। এই একটি কথা মনে রাখলে পরের অনেক নিয়ম আর মুখস্থ করতে হয় না। {}-এ কেন if চলে না, কেন class নয়, কেন একটি মাত্র root লাগে, সব উত্তর এখান থেকেই আসে।

নামের প্রথম অক্ষরেরও অর্থ আছে। ছোট হাতের <button /> compile হয় jsx("button", {})-এ, অর্থাৎ একটি string. আর বড় হাতের <Button /> হয় jsx(Button, {})-এ, অর্থাৎ Button নামের একটি ভ্যারিয়েবল। তাই component-এর নাম সবসময় বড় হাতের অক্ষরে শুরু করতে হয়। ছোট হাতে লিখলে React সেটিকে HTML tag ভাবে।

২ · {}: JSX-এর ভেতরে JavaScript-এর জানালা

কোঁকড়া বন্ধনী { } হলো JSX-এর ভেতরে খোলা একটি জানালা। তার ভেতরে যে কোনো JavaScript expression লেখা যায়। Expression মানে এমন কোড, যা চললে একটি মান তৈরি হয়। আর যে কোড একটি কাজ করে কিন্তু নিজে কোনো মান হয় না, তাকে বলে statement.

Expression, {}-এ চলেStatement, {}-এ চলে না
{name}, {qty * 120}const x = 5;
{qty > 0 ? 'আছে' : 'নেই'}if (qty > 0) { … }
{qty > 0 && <b>আছে</b>}switch (…) { … }
{items.map(i => …)}for (…) { … }

কারণটি আগের অংশেই আছে। <p>{x}</p> হয়ে যায় jsx("p", { children: x }). অর্থাৎ {}-এর ভেতরের জিনিস বসে একটি object-এর ঘরে। সেখানে মান লাগে। সাধারণ JavaScript-এ f(if (x) { … }) যেমন লেখা যায় না, এখানেও তাই। চেষ্টা করলে কী হয়, দেখুন (esbuild 0.28.2, সত্যিকারের run):

X [ERROR] Unexpected "if"

    bad/statement.jsx:2:13:
      2 │   return <p>{if (qty > 0) { 'আছে' }}</p>;
        ╵              ~~

তাহলে শর্ত লিখবেন কীভাবে? তিনটি পথ আছে। return-এর আগে সাধারণ if লিখে একটি ভ্যারিয়েবলে মান রাখা, {}-এর ভেতরে ? : ব্যবহার করা, অথবা &&. List আর শর্তের পূর্ণ আলোচনা পর্ব ১১-তে আছে।

{} কোথায় কোথায় বসে? দুই জায়গায়: লেখার জায়গায় (<h3>{name}</h3>) আর attribute-এর মান হিসেবে (<img src={url} />). Attribute-এ দুই রকম লেখার পার্থক্যটি ছোট, কিন্তু কাজের:

  • price="120" দিলে component পায় string '120'.
  • price={120} দিলে পায় number 120. হিসাবের জন্য এটিই চাই।
  • style={{ color: 'green' }}-এ দুই জোড়া বন্ধনী কোনো বিশেষ syntax নয়। বাইরেরটি জানালা, ভেতরেরটি একটি সাধারণ object.

এ ছাড়া তিনটি ছোট নিয়ম। প্রতিটি tag বন্ধ করতে হয়, তাই <br />, <img /> লিখতে হয়। মন্তব্য লিখতে হয় {/* এভাবে */}. আর return-এর পরে JSX নতুন লাইনে শুরু করলে বন্ধনী ( ) দিতে হয়, নইলে JavaScript সেখানেই return শেষ ধরে নেয়।

৩ · Attribute: কেন className, htmlFor আর camelCase

JSX দেখতে HTML-এর মতো, কিন্তু attribute-এর নাম আসলে একটি JavaScript object-এর key হয়ে যায়। React নাম নিয়েছে মূলত DOM property থেকে, যেগুলো camelCase-এ লেখা। তাই HTML থেকে কোড কপি করলে কয়েকটি জায়গা বদলাতে হয়।

HTML-এJSX-একেন
class="card"className="card"class JavaScript-এর সংরক্ষিত শব্দ, আর DOM property-র নামও className
for="q"htmlFor="q"for-ও সংরক্ষিত শব্দ; DOM property হলো htmlFor
onclick="…"onClick={fn}Event-এ লেখা নয়, ফাংশন দেওয়া হয়
tabindex="0"tabIndex={0}camelCase
style="font-size:18px"style={{ fontSize: 18 }}Style একটি object, string নয়
aria-label, data-idএকই থাকেএই দুই পরিবার হাইফেনসহই থাকে

Style object-এ সংখ্যা দিলে React নিজে px বসায়, তবে যেসব property-র একক নেই সেখানে নয়। সত্যিকারের run (React 19.3.1, renderToStaticMarkup):

jsx('p', { style: { fontSize: 18, lineHeight: 1.5, backgroundColor: 'red', marginTop: 0 }, children: 'x' })
→ <p style="font-size:18px;line-height:1.5;background-color:red;margin-top:0">x</p>

একটি অভ্যাসের কথা। React-এর docs নিজেই বলে, style রাখুন শুধু সেই মানের জন্য যা চলার সময় বদলায়। বাকি সব CSS class-এ রাখাই ভালো (className). Styling-এর বড় আলোচনা পর্ব ২২-এ।

আর event-এ সবচেয়ে সাধারণ ভুলটি দেখে রাখুন। onClick={addToCart} মানে “ক্লিক হলে এই ফাংশনটি চলবে”। কিন্তু onClick={addToCart()} মানে “ফাংশনটি এখনই, render-এর সময় চলবে, আর তার ফলই হবে handler”. প্রথমটিতে আপনি ফাংশন দিচ্ছেন, দ্বিতীয়টিতে ফাংশন ডাকছেন। Event নিয়ে পুরো আলোচনা পর্ব ১০-এ।

TypeScript এই ভুলগুলো চালানোর আগেই ধরে, এমনকি পরামর্শও দেয়। সত্যিকারের run (TypeScript 7.0.2, @types/react 19.3.0, আউটপুট সংক্ষিপ্ত করা):

Shelf.tsx(25,31): error TS2322: Type '{ class: string; }' is not assignable to type …
  Property 'class' does not exist on type … Did you mean 'className'?
Shelf.tsx(26,32): error TS2322: Type '{ for: string; children: string; }' is not assignable to type …
  Property 'for' does not exist on type … Did you mean 'htmlFor'?

৪ · Children, Fragment আর TSX-এ তাদের type

একটি tag-এর খোলা আর বন্ধ অংশের মাঝখানে যা থাকে, সেটিই children. আগের compile করা কোডে দেখেছেন, এটি আলাদা কিছু নয়; props-এর ভেতরে children নামের একটি সাধারণ ঘর। তাই আপনার নিজের component-ও এটি পড়তে পারে:

import type { ReactNode } from 'react';

type ShelfProps = {
  title: string;
  children: ReactNode;
};

function Shelf({ title, children }: ShelfProps) {
  return (
    <section>
      <h2>{title}</h2>
      {children}
    </section>
  );
}

// ব্যবহার: মাঝখানে যা দেবেন, সেটিই children
const ok = <Shelf title="চাল">{0}<p>মিনিকেট</p></Shelf>;

এখানে ReactNode হলো “React যা কিছু দেখাতে পারে” তার type: লেখা, সংখ্যা, element, array, null, undefined, boolean ইত্যাদি। পর্ব ০৬-এর TypeScript মনে থাকলে বুঝবেন, এটি একটি বড় union type. children: ReactNode লিখলে compiler বাধ্য করে যে children দিতেই হবে। দিতে ভুলে গেলে, বা সরু type-এ ভুল জিনিস দিলে, সত্যিকারের run-এ যা আসে:

// <Shelf title="ডাল" />
error TS2741: Property 'children' is missing in type '{ title: string; }' but required in type 'ShelfProps'.

// type OneChild = { children: string };  তারপর <Label><i>x</i></Label>
error TS2745: This JSX tag's 'children' prop expects type 'string' which requires multiple children, but only a single child was provided.

Children ঐচ্ছিক হলে লিখুন children?: ReactNode. দ্বিতীয় error-এর ভাষা একটু গোলমেলে, কিন্তু মূল কথা সোজা: এখানে string চাই, আপনি element দিয়েছেন। Component জোড়া দেওয়ার পুরো কৌশল পর্ব ০৯-এ।

একটি মাত্র root, আর Fragment

একটি ফাংশন একটি মানই ফেরত দিতে পারে। return jsx(...) jsx(...) কোনো বৈধ JavaScript নয়। তাই পাশাপাশি দুটি tag ফেরত দিলে error হয় (TypeScript 7.0.2, সত্যিকারের run):

two.tsx(3,5): error TS2657: JSX expressions must have one parent element.

সহজ সমাধান একটি <div>-এ মোড়ানো। কিন্তু কখনো বাড়তি div বসালে HTML-ই ভেঙে যায়। যেমন <dl>-এর ভেতরে dt আর dd জোড়া সরাসরি রাখতে চান। তখন লাগে Fragment: <> … </>. এটি কয়েকটি জিনিসকে একসাথে বাঁধে, কিন্তু DOM-এ কোনো tag তৈরি করে না।

function Row() {
  return (
    <>
      <dt>চাল</dt>
      <dd>৳৭৫</dd>
    </>
  );
}
// esbuild-এর সত্যিকারের অনুবাদ:
//   jsxs(Fragment, { children: [ jsx("dt", …), jsx("dd", …) ] })
// DOM-এ যা হয়:  <dt>…</dt><dd>…</dd>  (বাড়তি কোনো tag নেই)

List-এর ভেতরে Fragment-এ key দিতে চাইলে ছোট রূপটি চলে না; তখন <Fragment key={id}> লিখতে হয়। এর কারণ পর্ব ১১-তে পাবেন।

৫ · React element: UI-র বর্ণনা, একটি সাধারণ object

jsx() ডাকলে ফেরত আসে কী? কোনো DOM node নয়। ফেরত আসে একটি সাধারণ JavaScript object, যার নাম React element.

দর্জির কাছে গেলে তিনি খাতায় লেখেন: বুক ৪০, হাতা ২৪, কাপড় নীল। ওই কাগজটি শার্ট নয়, শার্টের বর্ণনা। কাগজ বানাতে কয়েক সেকেন্ড লাগে, শার্ট বানাতে অনেক বেশি। ভুল হলে কাগজ ফেলে নতুন করে লেখা যায়। React element হলো ওই কাগজ। আর আসল DOM হলো শার্ট। পুরোনো কাগজের সঙ্গে নতুন কাগজ মিলিয়ে React বোঝে শার্টে কোথায় বদল দরকার।

নিচের কোডটি চালান। প্রথম লাইনের আউটপুটই সেই object. খেয়াল করুন, createRoot(...).render() ডাকার আগে DOM-এ কিছুই নেই।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
const taka = 120;
const el = <p className="price">৳{taka}</p>;

console.log(el);
console.log('typeof:', typeof el);
console.log('type:', el.type);
console.log('children:', el.props.children);

const root = document.getElementById('root');
console.log('render-এর আগে DOM-এ element:', root.childElementCount);
createRoot(root).render(el);
setTimeout(() => {
  console.log('commit-এর পরে DOM:', root.innerHTML);
}, 50);
</script>

প্রথম লাইনের আউটপুট (React 19.3.1, এই পাতার সত্যিকারের run):

{ $$typeof: Symbol(react.transitional.element),
  type: 'p', key: null, ref: null,
  props: { className: 'price', children: [ '৳', 120 ] } }

পড়ার সুবিধায় লাইন ভাঙা হয়েছে; মান হুবহু একই। এরপর আসে typeof: object, type: p আর children: [ '৳', 120 ]. অর্থাৎ {taka} লেখাটি এখানে আর নেই, আছে তার মান 120. তারপর render-এর আগে DOM-এ element: 0, মানে object তৈরি হলেও DOM ফাঁকা। শেষে commit-এর পরে DOM: <p class="price">৳120</p>. $$typeof ঘরটি জানায় এটি সত্যিই React-এর বানানো element; এর কারণ ভাবনার প্রশ্নে আছে। এই live editor production build ব্যবহার করে। Development build-এ (Node-এ সত্যিকারের run) ref-এর বদলে দেখা যায় _owner আর _store, আর object-টি frozen থাকে: Object.isFrozen(el) দেয় true. তাই element বদলানোর চেষ্টা করবেন না, নতুন একটি বানান।

এবার পুরো পথটি এক নজরে ধাপে ধাপে দেখুন। “পরে” চাপতে থাকুন। ধরে নেওয়া হলো taka-র মান 120.

  1. JSX.jsx ফাইলআপনি লেখেন
  2. Compilejsxs()build-এর সময়
  3. Elementobjectচলার সময়
  4. DOM<p>commit-এর পরে
src/Price.jsxআপনার লেখা
const el = <p className="price">৳{taka}</p>;
Compile-এর পরেBrowser এটিই পায়
const el = jsxs("p", {
  className: "price",
  children: ["৳", taka]
});
চলার সময়ে elএকটি সাধারণ object
{
  $$typeof: Symbol(react.transitional.element),
  type: 'p',
  key: null,
  ref: null,
  props: { className: 'price', children: [ '৳', 120 ] }
}
DOMReact-এর বানানো আসল node
<p class="price">৳120</p>
↑ className হয়েছে class; "৳" আর 120 দুটি text node
  1. ধাপ ১ — JSX. আপনি লিখলেন একটি <p>, ভেতরে একটি স্থির লেখা ৳ আর একটি জানালা {taka}. এই রূপ browser বোঝে না।
  2. ধাপ ২ — Compile. Build টুল এটিকে jsxs() ডাকে বদলাল। সন্তান দুটি ("৳" আর taka), তাই jsx নয়, jsxs. এটি build-এর সময় একবারই হয়, প্রতি render-এ নয়।
  3. ধাপ ৩ — Element. চলার সময় jsxs() ডাকা হলো। ফিরল একটি object: type হলো 'p', আর props.children-এ এসেছে [ '৳', 120 ]. এখনো DOM-এ কিছুই বদলায়নি।
  4. ধাপ ৪ — DOM. render()-এর পর React object পড়ে একটি আসল <p> বানাল, className-কে class বসাল, আর লেখা দুটিকে দুটি text node করে জুড়ে দিল। Compile হয় build-এ, element তৈরি হয় প্রতি render-এ, আর DOM বদলায় শুধু commit-এ।

তিনটি জিনিস আলাদা রাখুন। Component হলো ফাংশন (নকশার নিয়ম)। Element হলো সেই ফাংশনের ফেরত দেওয়া object (একটি কাগজ)। DOM node হলো browser-এর আসল জিনিস (শার্ট)। ইন্টারভিউতে “component আর element-এর পার্থক্য কী” খুব পরিচিত প্রশ্ন। <ProductCard /> লিখলে ফাংশনটি তখনই চলে না। তৈরি হয় type: ProductCard লেখা একটি element, আর ফাংশনটি ডাকে React, render ধাপে।

৬ · কোন মান পর্দায় আসে: 0 আর &&-এর ক্লাসিক বাগ

{}-এ যা দেবেন, React সেটি নিয়ে কী করবে? উত্তরটি মানের ওপর নির্ভর করে। নিচের প্রতিটি সারি সত্যিকারের run (React 19.3.1, <p>-এর children হিসেবে দেওয়া):

{}-এ দেওয়া মানপাওয়া HTMLঅর্থ
true, false<p></p>কিছু দেখায় না
null, undefined<p></p>কিছু দেখায় না
"" (খালি string)<p></p>কিছু দেখায় না
[]<p></p>কিছু দেখায় না
0<p>0</p>পর্দায় 0 আসে
NaN<p>NaN</p>পর্দায় NaN আসে, development-এ warning-ও
[1, 2]<p>12</p>Array-র প্রতিটি উপাদান পরপর
{ a: 1 }কিছু তৈরি হয় নাError: Objects are not valid as a React child

true কেন দেখায় না, সেটিই এই ব্যবস্থার সুবিধা: এতে {cond && <x />} লেখা যায়। false হলে সেখানে কিছুই বসে না। React-এর docs এই জায়গাকে JSX গাছের একটি “গর্ত” বলে। কিন্তু 0 তো একটি বৈধ তথ্য। “আপনার কার্টে 0টি পণ্য” দেখানোই তো লাগতে পারে। তাই React সেটি দেখায়।

এবার গোলমালটি বুঝতে JavaScript-এর একটি নিয়ম মনে করুন। a && b কোনো boolean ফেরত দেয় না। এটি একটি short-circuit operator: a falsy হলে a-কেই ফেরত দেয়, নইলে b ফেরত দেয়। (এই নিয়ম JavaScript কোর্সের মান ও type-এর পর্বে আছে।) 0 falsy, তাই 0 && <b>…</b>-এর ফল false নয়, স্বয়ং 0.

বাজারঘরে ঠিক এই ঘটনা ঘটল। পণ্য ফুরিয়ে গেলে কার্ডে একটি নিঃসঙ্গ 0 দেখা দিল। পাশাপাশি দুটি component চালিয়ে দেখুন:

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
function Buggy({ stock }) {
  return <p>ভুল: {stock && <b>{stock}টি আছে</b>}</p>;
}
function Fixed({ stock }) {
  return <p>ঠিক: {stock > 0 && <b>{stock}টি আছে</b>}</p>;
}
function App() {
  const [stock, setStock] = useState(0);
  useLayoutEffect(() => {
    const [a, b] = document.querySelectorAll('p');
    console.log('stock', stock, '→', a.textContent, '/', b.textContent);
  });
  return (
    <div>
      <Buggy stock={stock} />
      <Fixed stock={stock} />
      <button onClick={() => setStock(stock + 1)}>স্টক +১</button>
      <button onClick={() => setStock(0)}>শূন্য করুন</button>
    </div>
  );
}
createRoot(document.getElementById('root')).render(<App />);
</script>

Run করলেই আসে stock 0 → ভুল: 0 / ঠিক:. “ভুল” লাইনে একটি শূন্য বসে আছে, “ঠিক” লাইনটি ফাঁকা। “স্টক +১” চাপলে আসে stock 1 → ভুল: 1টি আছে / ঠিক: 1টি আছে. দুটি একই দেখায়, আর এ কারণেই বাগটি চোখ এড়ায়। “শূন্য করুন” চাপলে আবার আসে stock 0 → ভুল: 0 / ঠিক:. দুটি কোডের পার্থক্য শুধু &&-এর বাম দিকে: প্রথমটিতে একটি সংখ্যা, দ্বিতীয়টিতে একটি boolean.

1function Stock({ stock }) {2  const bug = stock && <b>আছে</b>;3  const fix = stock > 0 && <b>আছে</b>;4  return <p>{bug}</p>; // সংশোধনে {fix}5}
মান (Watch)
stock
&&-এর বাম দিক
পুরো ফল
  1. stock = 3. বাম দিক 3, যা truthy. তাই && ডান দিকের <b> element ফেরত দিল। পর্দায় “আছে”। সব ঠিক, তাই বাগটি পরীক্ষায় ধরা পড়ে না।
  2. stock = 0, হিসাব। পণ্য ফুরাল। বাম দিক 0, যা falsy. && ডান দিকে যায়ই না, আর বাম দিকের মানটিই ফেরত দেয়। ফল false নয়, 0.
  3. React পেল 0. Number হলো দেখানোর মতো মান। তাই React লিখল <p>0</p>. ক্রেতা পর্দায় একটি অর্থহীন শূন্য দেখলেন। এটিই বাগ।
  4. সংশোধন: তুলনা। এবার লাইন ৩ চলছে। stock > 0 মানে 0 > 0, যা false. বাম দিকটি এখন সত্যিকারের boolean, তাই পুরো ফলও false.
  5. React পেল false. false মানে JSX গাছের গর্ত। তাই <p></p>, ভেতরে কিছু নেই। বাম দিকে সবসময় boolean রাখলে এই বাগ আর হবে না।

সারানোর তিনটি পথ আছে। stock > 0 && … সবচেয়ে পরিষ্কার। stock ? … : null-ও ভালো। !!stock && … চলে, তবে পড়তে কম সহজ। এই বাগের সবচেয়ে পরিচিত রূপ হলো items.length && …, তাই সেখানে items.length > 0 লিখুন। একই ধরনের ভুল NaN-এও হয়। ESLint-এর জন্য eslint-plugin-react-এ jsx-no-leaked-render নামে একটি নিয়ম আছে, যা এই ধরনের লেখা ধরতে পারে।

৭ · Escaping: JSX লেখাকে লেখাই রাখে

বাজারঘরে ক্রেতারা পণ্যের review লেখেন। ধরুন কেউ লিখলেন <b>খুব ভালো</b>. এই লেখা আপনি {review} দিয়ে দেখালে কী হবে? মোটা অক্ষর হবে? হবে না। আপনার JSX-এর {}-এ আসা string সবসময় লেখা হিসেবেই পর্দায় যায়, HTML হিসেবে নয়। একে বলে escaping.

কেন এত গুরুত্বপূর্ণ? কারণ কেউ <b>-এর জায়গায় ক্ষতিকর কোড পাঠাতে পারত, আর সেটি অন্য ক্রেতার browser-এ চলত। এই আক্রমণের নাম XSS. এর তত্ত্ব, ধরন আর প্রতিরক্ষা Cybersecurity কোর্সের XSS ও CSP পর্বে আছে। এখানে শুধু React-এর দিকটি দেখি। সত্যিকারের run (React 19.3.1, server-এ HTML বানানোর সময়):

jsx('p', { children: '<b>দাম কম!</b> & "ছাড়"' })
→ <p>&lt;b&gt;দাম কম!&lt;/b&gt; &amp; &quot;ছাড়&quot;</p>

Browser-এ ব্যাপারটি আরও সরল। React লেখা বসায় text node হিসেবে, তাই সেখানে HTML বিশ্লেষণের প্রশ্নই ওঠে না। নিচে একটি নিরীহ <b> দিয়ে দুই পথের পার্থক্য দেখুন:

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
const fromUser = '<b>আজ ৫০% ছাড়!</b>';
const trusted = { __html: '<b>আমাদের নিজের লেখা</b>' };

function App() {
  return (
    <div>
      <p id="a">{fromUser}</p>
      <p id="b" dangerouslySetInnerHTML={trusted} />
    </div>
  );
}
createRoot(document.getElementById('root')).render(<App />);
setTimeout(() => {
  const a = document.getElementById('a');
  const b = document.getElementById('b');
  console.log('a-র innerHTML:', a.innerHTML);
  console.log('a-র ভেতরে element:', a.childElementCount);
  console.log('b-র innerHTML:', b.innerHTML);
  console.log('b-র ভেতরে element:', b.childElementCount);
}, 50);
</script>

আউটপুট আসে এই চার লাইন:

a-র innerHTML: &lt;b&gt;আজ ৫০% ছাড়!&lt;/b&gt;
a-র ভেতরে element: 0
b-র innerHTML: <b>আমাদের নিজের লেখা</b>
b-র ভেতরে element: 1

প্রথম লাইনে < হয়ে গেছে &lt;, আর ভেতরে কোনো element তৈরি হয়নি (0). তাই পর্দায় <b> অক্ষরগুলো সরাসরি দেখা যায়। দ্বিতীয় ক্ষেত্রে একটি সত্যিকারের b element তৈরি হয়েছে (1), আর লেখা মোটা হয়েছে। এখানে এটি নিরাপদ, কারণ string-টি আমাদের নিজের কোডে লেখা একটি ধ্রুবক। আর string-টি ব্যবহারকারীর হলেই পুরো ঝুঁকি আপনার।

dangerouslySetInnerHTML নামটি ইচ্ছা করেই লম্বা আর ভয়ের। এটি DOM-এর innerHTML-কে সরাসরি বসায়, আর React তখন কোনো escaping করে না। মান দিতে হয় { __html: … } রূপে, যাতে হাত ফসকে কোনো সাধারণ string এখানে চলে না আসে। একই tag-এ children আর এটি একসাথে দেওয়া যায় না।

নিয়ম: ব্যবহারকারীর লেখা বা বাইরের কোনো উৎসের HTML সরাসরি এখানে দেবেন না। সত্যিই HTML দেখাতে হলে (যেমন admin-এর লেখা blog পোস্ট) আগে একটি পরিচিত sanitizer library দিয়ে sanitize করুন, যেমন DOMPurify, আর যতটা সম্ভব server-এই করুন। অতিরিক্ত স্তর হিসেবে CSP রাখুন।

Escaping সব ফাঁক বন্ধ করে না, এটিও জানা দরকার। এটি লেখাকে সুরক্ষা দেয়, URL-এর অর্থ নয়। ব্যবহারকারীর দেওয়া লিংক href={url}-এ বসালে সেটি javascript: দিয়ে শুরু হতে পারে। React 19 এখন এমন URL আটকে দেয়। সত্যিকারের run-এ href="javascript:alert(1)" বদলে হয়ে গেল javascript:throw new Error('React has blocked a javascript: URL as a security precaution.'). তবু এর ওপর পুরোপুরি ভরসা না করে নিজে যাচাই করুন: শুধু https: লিংক গ্রহণ করুন। Input যাচাইয়ের বিস্তারিত পর্ব ২৭-এ, আর অ্যাপের পুরো নিরাপত্তা তালিকা পর্ব ৫০-এ আছে।

৮ · Trigger, render, commit: পর্দা বদলের তিন ধাপ

“Render” শব্দটি নিয়ে সবচেয়ে বেশি ভুল বোঝাবুঝি হয়। পর্ব ০৩-এর Rendering-এর মানচিত্রে শব্দটির অর্থ ছিল HTML কোথায় তৈরি হয়। React-এর ভেতরে এর অর্থ আরও সরু: render মানে React আপনার component ফাংশনটি ডাকছে। এটি পর্দা আঁকা নয়। React-এর docs রেস্তোরাঁর উদাহরণ দেয়, আর এটি আমাদের দেশেও মিলে যায়:

পর্দা বদলের ধাপ: রেস্তোরাঁর মতো

Trigger, render, commit, then the browser paints

Trigger থেকে render, commit হয়ে paint, আর আবার trigger-এ ফেরা ১ · Trigger ওয়েটার অর্ডার নিলেন root.render() · setQty() ২ · Render রান্নাঘরে রান্না হলো React component ডেকে element পেল ৩ · Commit খাবার টেবিলে পৌঁছাল React শুধু দরকারি DOM বদলাল Paint (browser-এর কাজ) পর্দায় পিক্সেল আঁকা হলো React-এর বাইরে, commit-এর পরে
ছবি ৮.১ — ডান পাশের ডটেড রেখা: state আবার বদলালে চক্রটি আবার শুরু হয়।

এই ধাপগুলোর নির্দিষ্ট অর্থ আছে:

  • Trigger: কেউ React-কে জানাল যে update দরকার।
  • Render: React component ফাংশন ডাকে, আর যেসব component তার ভেতরে আছে সেগুলোকেও একে একে ডাকে। এই ধাপে DOM-এ হাত পড়ে না। ফলে render একটি pure function-এর মতো হওয়া উচিত: একই ইনপুটে একই ফল, আর বাইরের কিছুতে বদল নয়।
  • Commit: React শুধু দরকারি DOM অংশটুকু বদলায়। কোন অংশ দরকারি, সেটি render ধাপেই নতুন আর পুরোনো বর্ণনা মিলিয়ে ঠিক হয়ে থাকে। এই মেলানোর নাম reconciliation, যার ভেতরটা পর্ব ১৫-এ দেখবেন।
  • Paint: এটি browser-এর কাজ, React-এর নয়।

একটি ক্লিকের পর ঠিক কী কখন বদলায়, ধাপে ধাপে দেখুন। বিশেষ করে খেয়াল করুন, state, DOM আর পর্দা একই মুহূর্তে বদলায় না।

1function Cart() {2  const [qty, setQty] = useState(1);3  const add = () => setQty(qty + 1);4  return <button onClick={add}>কার্ট: {qty}</button>;5}
কোথায় কী আছে
এই render-এর qty
সর্বশেষ element
DOM-এর লেখা
পর্দার পিক্সেল
  1. অপেক্ষায়। পর্দায় “কার্ট: 1”. চারটি জায়গাতেই একই মান। React কিছুই করছে না।
  2. Trigger. ক্রেতা ক্লিক করলেন, লাইন ৩ চলল, setQty(2) ডাকা হলো। React একটি update সারিতে রাখল। এখনো কিছু বদলায়নি। এই render-এর qty তখনো 1.
  3. Render. Event handler শেষ হলে React আবার Cart() ডাকল। এবার useState দিল 2. ফাংশন ফেরত দিল নতুন element, “কার্ট: 2”. DOM তখনো “কার্ট: 1”.
  4. Render শেষ: পার্থক্য হিসাব। React পুরোনো আর নতুন element মেলাল। Tag একই (button), "কার্ট: " একই, শুধু সংখ্যার লেখা আলাদা। তাই একটি মাত্র বদল দরকার। DOM এখনো ছোঁয়া হয়নি।
  5. Commit. React শুধু সেই একটি text node বদলাল। পুরো button আবার তৈরি হয়নি। এখন DOM-এ “কার্ট: 2”, তবে পিক্সেল তখনো পুরোনো।
  6. Paint. Browser নতুন পিক্সেল আঁকল। ক্রেতা এখন “কার্ট: 2” দেখছেন। পুরো যাত্রায় আপনার কোড DOM-এ একবারও হাত দেয়নি।

এবার একই ক্রম আসল React-এ দেখুন। useLayoutEffect এমন একটি hook, যার ভেতরের কোড commit-এর ঠিক পরে, paint-এর আগে চলে; তাই এখানে এটি commit-এর চিহ্ন হিসেবে কাজ করছে। এর পুরো আলোচনা পর্ব ১৩-তে। Run করুন, তারপর button-এ দুবার ক্লিক করুন।

<div id="root"></div>
<script src="../course-kit/vendor/react-lab.js"></script>
<script type="text/jsx">
function Counter() {
  const [n, setN] = useState(0);
  const dom = document.getElementById('c')?.textContent ?? 'নেই';
  console.log('২. render: n =', n, '· DOM-এ এখনো:', dom);
  useLayoutEffect(() => {
    const now = document.getElementById('c').textContent;
    console.log('৩. commit শেষ: DOM-এ এখন:', now);
  });
  function add() {
    console.log('১. trigger: setN ডাকা হলো');
    setN(n + 1);
    console.log('   setN-এর পরেও এই n =', n);
  }
  return <button id="c" onClick={add}>{n}</button>;
}
console.log('১. trigger: root.render ডাকা হলো');
createRoot(document.getElementById('root')).render(<Counter />);
</script>

Run করলে প্রথম তিন লাইন আসে, তারপর একটি ক্লিকে পরের চার লাইন (এই পাতায় সত্যিকারের run, Edge):

১. trigger: root.render ডাকা হলো
২. render: n = 0 · DOM-এ এখনো: নেই
৩. commit শেষ: DOM-এ এখন: 0
১. trigger: setN ডাকা হলো
   setN-এর পরেও এই n = 0
২. render: n = 1 · DOM-এ এখনো: 0
৩. commit শেষ: DOM-এ এখন: 1

তিনটি জিনিস দেখুন। এক, ক্রম সবসময় trigger, render, commit. দুই, setN(n + 1)-এর পরের লাইনে n তখনো 0. Set ফাংশন এই render-এর ভ্যারিয়েবল বদলায় না, পরের render-এর জন্য মান রাখে। পর্ব ১০-এ একে বলা হবে snapshot. তিন, render চলার সময় DOM পুরোনো মানই দেখাচ্ছে (DOM-এ এখনো: 0). দ্বিতীয় ক্লিকেও ঠিক একই নকশা আসে, শুধু সংখ্যা এক বেশি। Render-এর ভেতরে DOM পড়া এখানে শুধু দেখানোর জন্য; আসল কোডে এমন করবেন না।

Re-render ঠিক কখন হয়

একটি component-এর re-render মানে তাকে আবার ডাকা। React-এর নিজের docs দুটি মূল কারণ বলে: প্রথমবার দেখানো, আর state-এর বদল। বাস্তব কোডে সেটি এই তালিকায় দাঁড়ায়:

ঘটনাRe-render?মন্তব্য
root.render(), প্রথমবারহ্যাঁপুরো গাছের প্রথম render
নিজের state-এর set ফাংশন, নতুন মানহ্যাঁসবচেয়ে সাধারণ trigger
Parent component re-render হলোহ্যাঁProps না বদলালেও, ডিফল্টভাবে সন্তানও চলে
ব্যবহার করা Context-এর মান বদলালহ্যাঁপর্ব ১৭-এ আসছে
Set ফাংশনে একই মান দেওয়া (Object.is)সাধারণত নাReact বাদ দেয়; কখনো ফাংশন একবার ডেকে তারপর থামে
সাধারণ ভ্যারিয়েবল বদলানোনাReact এই বদলের কথা জানেই না
Props object-এর ভেতরে হাতে বদলনাTrigger হয় না, আর এটি নিয়মবিরুদ্ধও

লক্ষ করুন, “props বদলানো” নিজে কোনো trigger নয়। Props নতুন আসে parent render হওয়ার কারণেই। আর এক event-এর ভেতরে কয়েকটি set ডাকলে React সেগুলো একসাথে করে একটি render চালায়; একে বলে batching. বিস্তারিত পর্ব ১০-এ।

একটি হিসাব: ২০০টি কার্ড, একটি সংখ্যা

কাজের হিসাব

বাজারঘরের একটি পাতায় App-এর ভেতরে একটি কার্ট-button আর ২০০টি Card. প্রতিটি কার্ডে ৪টি element (div, h3, p, button). কার্ট-button-এ একবার ক্লিক করলে App-এর state বদলায়।

DOM element: main ১ + button ১ + ২০০ × ৪ = ৮০২টি.
Render ধাপ: App ১ + Card ২০০ = ২০১টি ফাংশন ডাক.
Commit ধাপ: একটি text node বদল।

এটি আমরা React 19.3.1-এ MutationObserver দিয়ে Edge-এ মেপে দেখেছি (একটি স্থানীয় run). পেয়েছি {"app":1,"card":200} আর মাত্র একটি পরিবর্তন: characterData:#text. অর্থাৎ re-render মানে DOM বদল নয়। ২০১টি ডাক থেকে DOM-এ বদল এল ১টি।

সময়ের অনুমান: ৬০ Hz পর্দায় প্রতি ছবির জন্য সময় থাকে ১০০০ ÷ ৬০ ≈ ১৬.৭ ms, যাকে বলে frame budget. ধরে নিই (মাপা নয়, সহজ অনুমান) একটি Card render হতে ০.০৫ ms লাগে। তাহলে ২০০ × ০.০৫ = ১০ ms, অর্থাৎ বাজেটের প্রায় ৬০%. Card ভারী হলে বা সংখ্যা বাড়লে এটিই ধীরগতির কারণ হয়। এর চিকিৎসা (memo, state নিচে নামানো) আসবে পর্ব ২৩-এ।

আরও গভীরে: Render আর commit আলাদা বলেই React render-এর কাজ টুকরো করতে পারে, মাঝপথে থামাতে পারে, এমনকি বাতিলও করতে পারে; এই ক্ষমতার ওপরই concurrent rendering দাঁড়িয়ে। Commit কিন্তু একটানা হয়, যাতে ব্যবহারকারী আধা-বদলানো পর্দা না দেখেন। এ কারণেই development-এ Strict Mode component দুবার ডেকে অশুদ্ধ (impure) কোড ধরিয়ে দেয়। আর jsxDEV নামের development রূপটি ফাইলের নাম ও লাইন নম্বর সঙ্গে নেয় (esbuild-এর --jsx-dev-এ দেখা যায়), যাতে error-এ ঠিক জায়গা দেখানো যায়।

৯ · বাংলা লেখা আর সংখ্যা: Intl দিয়ে দেখানো

JSX-এ বাংলা লেখা সরাসরি লেখা যায়, ফাইলটি UTF-8 হলেই হলো। সমস্যা আসে সংখ্যায়। {1234567.5} লিখলে পর্দায় আসে 1234567.5. বাংলাদেশের পাঠক পড়েন “১২ লাখ ৩৪ হাজার”। হাতে কমা বসানো বা অঙ্ক বদলানোর ফাংশন লেখার দরকার নেই। Browser আর Node, দুই জায়গাতেই আছে Intl API. সত্যিকারের run (Node 24.15, bn-BD locale):

const n = 1234567.5;
new Intl.NumberFormat('bn-BD').format(n);
new Intl.NumberFormat('bn-BD', { style: 'currency', currency: 'BDT' }).format(n);
new Intl.NumberFormat('bn-BD', { style: 'currency', currency: 'BDT',
                                 maximumFractionDigits: 0 }).format(120);
new Intl.NumberFormat('en-US').format(n);
new Intl.DateTimeFormat('bn-BD', { dateStyle: 'long', timeZone: 'Asia/Dhaka' })
  .format(new Date('2026-11-01T06:00:00Z'));
new Intl.ListFormat('bn-BD', { type: 'conjunction' }).format(['চাল', 'ডাল', 'তেল']);
১২,৩৪,৫৬৭.৫
১২,৩৪,৫৬৭.৫০৳
১২০৳
1,234,567.5
১ নভেম্বর, ২০২৬
চাল, ডাল এবং তেল

লাখ-হাজারের কমা, বাংলা অঙ্ক, মুদ্রার চিহ্ন, সব নিজে থেকে এল। React-এ এর ব্যবহার সহজ: state-এ সংখ্যাই রাখুন, আর render-এর সময় format করুন।

// একবার বানিয়ে রাখা, যাতে প্রতি render-এ নতুন করে না বানাতে হয়
const taka = new Intl.NumberFormat('bn-BD', {
  style: 'currency', currency: 'BDT', maximumFractionDigits: 0,
});

export function Price({ amount }) {
  return <p className="price">{taka.format(amount)}</p>;
}
// <Price amount={120} />  →  ১২০৳

তিনটি সতর্কতা মনে রাখুন। এক, চিহ্নটি সংখ্যার পরে বসেছে, কারণ এই ICU ডেটায় bn-BD-এর নিয়ম তাই; চিহ্ন আগে চাইলে নিজের ডিজাইন অনুযায়ী সাজাতে হবে। দুই, Intl-এর ফল ICU ডেটার version-এর ওপর নির্ভর করে, তাই test-এ এই লেখা হুবহু মেলানো ঝুঁকিপূর্ণ। তিন, server আর browser-এর timezone বা ICU আলাদা হলে দুই জায়গার লেখা অমিল হতে পারে, তাই timeZone সবসময় নির্দিষ্ট করে দিন। এর ফল কী হয়, তা hydration-এর আলোচনায় পর্ব ৫৬-এ দেখবেন। পুরো বাংলা সাইটের কৌশল (font, locale routing) পর্ব ৫৯-এ আছে।

১০ · AI-এর যুগে JSX: কী দেখে যাচাই করবেন

JSX এমন একটি জায়গা, যেখানে AI খুব দ্রুত কোড দেয়, আর সেই কোড সাধারণত চলেও। তাই ভুলগুলো চোখে পড়ে পরে। সমস্যা হলো AI পুরোনো কোড থেকেও শিখেছে, আর “চলছে” মানেই “ঠিক” নয়। নিচের ভুলগুলো বারবার দেখা যায়:

AI যা লেখেসমস্যাকীভাবে ধরবেন
ReactDOM.render(…)React 19-এ সরানো হয়েছে; এখন createRootআপনার version-এর react.dev
গ্লোবাল JSX.ElementReact 19-এর type-এ গ্লোবাল JSX নেই; React.JSX.Element বা ReactNodetsc চালান
{items.length && …}খালি list-এ পর্দায় 0খালি ডেটা দিয়ে চালিয়ে দেখুন
dangerouslySetInnerHTML={{ __html: comment }}কোনো error ছাড়াই XSS-এর দরজা খোলাএই নামটি কোডে search করুন
onClick={save()}Render-এর সময়ই ডাকা হয়ক্লিক ছাড়াই log আসছে কি না দেখুন
{price || 'দাম নেই'}দাম 0 হলেও “দাম নেই”; ভুল চুপচাপ ঢাকা পড়েEdge case দিন, প্রয়োজনে ??
বাংলা সংখ্যার জন্য একটি package-এর নাম, যা AI নিজেই বানিয়েছেPackage না-ও থাকতে পারে, থাকলেও অচেনা কারও হতে পারে; Intl-ই যথেষ্টnpm view <নাম>, আর diff পড়ুন
JSX-এ API key বসানোBrowser-এর bundle-এ গোপন মান ফাঁসBuild করা ফাইলে খুঁজুন; পর্ব ৭৮ দেখুন

AI-কে এমনভাবে কাজ দিন, যাতে version আর সীমা স্পষ্ট থাকে। শেষ দুটি লাইনই আপনাকে যাচাইয়ের সুযোগ দেয়:

আমি React 19.3 আর TypeScript দিয়ে একটি অনলাইন শপের পণ্যের কার্ড বানাচ্ছি। নিচের TSX component-টি review করো। [এখানে আপনার কোড পেস্ট করুন] ১. কোথাও 0, NaN বা খালি string পর্দায় ভুলভাবে আসতে পারে কি না দেখো। ২. dangerouslySetInnerHTML বা ব্যবহারকারীর দেওয়া URL আছে কি না দেখো; থাকলে ঝুঁকি বলো। ৩. React 19-এ সরানো কোনো API ব্যবহার হয়েছে কি না বলো, আর react.dev-এর কোন পাতায় মিলিয়ে দেখব তা জানাও। ৪. কোনো নতুন package যোগ করো না। পুরো কোড আবার দিও না; শুধু যে লাইন বদলাতে হবে তা দেখাও এবং কেন, তা বলো।

AI-এর JSX হাতে পেলে: যাচাইয়ের চেকলিস্ট

পরীক্ষা ও ইন্টারভিউতে:

  • JSX কী? JavaScript-এর একটি syntax extension, যা build-এর সময় jsx() ডাকে বদলায়; browser এটি সরাসরি চালায় না।
  • Component আর element-এর পার্থক্য? Component ফাংশন; element তার ফেরত দেওয়া সাধারণ object.
  • Render আর commit-এর পার্থক্য? Render-এ React component ডাকে, DOM বদলায় না; commit-এ দরকারি DOM বদলায়।
  • count && <x />-এ কী গোলমাল? count 0 হলে && 0-ই ফেরত দেয়, আর React সেটি দেখায়। সমাধান count > 0 &&.
  • Re-render মানেই কি DOM বদল? না। React পার্থক্য হিসাব করে শুধু যা আলাদা, তা-ই বদলায়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১Markup আর logic এক ফাইলে রাখা কি separation of concerns নীতি ভাঙে না?

২০১৩ সালে React প্রকাশের পর অনেকের প্রথম প্রতিক্রিয়া ছিল ঠিক এটাই। বহু বছর ধরে শেখানো হতো, HTML এক ফাইলে, CSS আরেক ফাইলে, JavaScript আরেক ফাইলে রাখতে হবে। JavaScript-এর ভেতরে tag দেখে অনেকের মনে হয়েছিল পিছিয়ে যাওয়া। সে বছর React দলের Pete Hunt একটি বক্তৃতায় (“Rethinking Best Practices” নামে পরিচিত) যুক্তি দেন: আগের নিয়ম আসলে concern আলাদা করে না, আলাদা করে প্রযুক্তি.

কথাটি ভাবুন। একটি কার্ডের HTML আর সেই কার্ডের stock > 0 শর্ত কি সত্যিই আলাদা দায়িত্ব? শর্ত বদলালে markup বদলায়, markup বদলালে শর্ত। এ দুটির cohesion বেশি, কিন্তু দুটিকে দুই ফাইলে রাখলে coupling শুধু লুকিয়ে যায়, কমে না। template ভাষায় তখন নিজস্ব শর্ত আর লুপের syntax শিখতে হতো। React বলল, JavaScript তো আছেই; নতুন ভাষা কেন?

তবে এটি শেষ কথা নয়। Vue আর Svelte এখনো template পছন্দ করে। Template সীমিত বলে compiler আগেই বেশি কিছু জানতে পারে, যা optimization-এ সাহায্য করে; JSX পুরো JavaScript বলে তার আচরণ আগে থেকে বোঝা কঠিন। পর্ব ৬৬ আর ৬৭-তে তাদের দর্শন দেখবেন। আর JSX-ও সব দায়িত্ব এক ফাইলে রাখতে বলে না। ডেটা আনা, ব্যবসার নিয়ম আর server-এর কাজ আলাদা রাখা আজও দরকার (পর্ব ০৪)।

মূল উপলব্ধি: Separation of concerns মানে ফাইলের ধরন আলাদা করা নয়, বরং আলাদা কারণে বদলায় এমন জিনিস আলাদা রাখা। একসাথে বদলায় এমন জিনিস একসাথেই থাকা ভালো।

প্র ০২React element-এ $$typeof নামের অদ্ভুত ঘরটি কেন? শুধু type আর props থাকলেই তো হতো।

এর পেছনে একটি নিরাপত্তার গল্প আছে, যা Dan Abramov ২০১৮ সালের একটি লেখায় বুঝিয়েছেন। Element যেহেতু সাধারণ object, তাই একটি ঝুঁকি ছিল। ধরুন একটি server-এ ত্রুটি আছে, যেখানে ব্যবহারকারী লেখার বদলে একটি JSON object জমা রাখতে পারেন। অ্যাপ সেটি পড়ে {message.text} দেখাচ্ছে, আর ভাবছে এটি string.

আক্রমণকারী যদি JSON-এ এমন একটি object রাখেন, যা দেখতে element-এর মতো, আর তার props-এ dangerouslySetInnerHTML থাকে, তাহলে React সেটিকে আসল element ভেবে HTML বসিয়ে দিতে পারত। পুরোনো React (০.১৩) এ ঝুঁকিতে ছিল। লক্ষ করুন, escaping এখানে কাজে আসত না, কারণ জিনিসটি string-ই নয়।

সমাধানটি চমৎকার। React ০.১৪ থেকে element-এ একটি চিহ্ন বসায়, যার মান একটি Symbol. JSON-এ Symbol রাখা যায় না; JSON.stringify সেটিকে বাদ দেয়, আর JSON.parse সেটি বানাতে পারে না। তাই বাইরের ডেটা থেকে আসা object-এ এই চিহ্ন থাকা সম্ভব নয়, আর React চিহ্ন না পেলে সেটি প্রত্যাখ্যান করে। এ জন্যই কোনো সাধারণ object দিলে আপনি দেখেন Objects are not valid as a React child. Symbol.for ব্যবহারের কারণ, এই Symbol iframe বা worker-এর মধ্যেও একই থাকে। React 19-এ চিহ্নটির নাম হয়েছে react.transitional.element, যা এই পর্বের আউটপুটেই দেখেছেন।

একটি শর্ত মনে রাখুন: আক্রমণটির জন্য আগে server-এ একটি ফাঁক থাকতে হয়। React-এর চিহ্ন একটি বাড়তি বেড়া, server-এর যাচাইয়ের বিকল্প নয়।

মূল উপলব্ধি: নিরাপত্তা মাঝে মাঝে ভাষার খুব ছোট একটি বৈশিষ্ট্যে লুকিয়ে থাকে। “এই মান কোথা থেকে এল এবং সেটি কি জাল করা সম্ভব” প্রশ্নটি যেকোনো ডেটা-ভিত্তিক নকশায় কাজে লাগে।

প্র ০৩Render আর commit আলাদা কেন? সরাসরি DOM বদলে দিলেই তো হতো।

প্রথমে সরল প্রশ্ন: প্রতিটি হিসাবের সঙ্গে সঙ্গে DOM বদলালে কী হতো? ধরুন একটি ক্লিকে ২০০টি কার্ডের ৩টি বদলাবে, আর একটি সারাংশ। হিসাব চলতে চলতে DOM বদলালে দুটি সমস্যা হয়। এক, পর্দা আধা-বদলানো অবস্থায় থাকতে পারে: সংখ্যা নতুন, মোট পুরোনো। পর্ব ০১-এ দেখা স্ক্রিন আর তথ্যের অমিল আবার ফিরে আসে। দুই, DOM পড়া আর লেখা মিশে গেলে browser-কে বারবার layout হিসাব করতে হতে পারে, যা ধীর।

আলাদা ধাপের সুবিধা হলো, render একটি পরীক্ষার খাতার মতো। খাতায় যত খুশি হিসাব করুন, পাকা হিসাবে ওঠে শেষে একবারে। Render কাগজে হয় বলে সেটি থামানো যায়, বাতিল করা যায়, আবার শুরু করা যায়। ব্যবহারকারী search box-এ দ্রুত টাইপ করলে React আধা-করা ভারী render ফেলে দিয়ে নতুনটি ধরতে পারে। Concurrent rendering, useTransition আর Suspense-এর এই ক্ষমতা আসে এই বিভাজন থেকেই। Commit হয় একটানা, তাই পর্দা সবসময় একটি সম্পূর্ণ অবস্থা দেখায়।

এর একটি দামও আছে। Render যেহেতু একাধিকবার হতে পারে বা বাতিল হতে পারে, তাই component ফাংশনের ভেতর বাইরের কিছু বদলানো চলে না। সেখানে API call, localStorage লেখা বা বাইরের ভ্যারিয়েবল বাড়ানো ঠিক নয়। এ জন্যই React “pure” থাকার ওপর এত জোর দেয়, আর Strict Mode development-এ ফাংশন দুবার ডেকে এমন ভুল ফাঁস করে। বাইরের কাজের জায়গা event handler আর effect (পর্ব ১৩)।

আরও একটি লাভ: render ধাপের ফল যেহেতু শুধু বর্ণনা, তাই একই বর্ণনা server-এ HTML-এ বদলানো যায়। এই কারণেই একই component পর্ব ৫৫-এর SSR-এ চলতে পারে।

মূল উপলব্ধি: “হিসাব” আর “প্রভাব” আলাদা করলে হিসাবকে ইচ্ছামতো থামানো, বাতিল ও পরীক্ষা করা যায়। ডেটাবেসের transaction-এর commit-এর সঙ্গে এর মিল মোটেই কাকতালীয় নয়।

প্র ০৪dangerouslySetInnerHTML এত ভয়ের নামে কেন, আর বাস্তবে কখন এটি লাগে?

নামটিই একটি নিরাপত্তা-কৌশল। সাধারণ DOM-এ el.innerHTML = x দেখতে একটি নিরীহ লাইন। Code review-তে সহজে চোখ এড়ায়। React ইচ্ছা করেই এমন নাম রেখেছে, যা লিখতে অস্বস্তি হয় আর review-তে চোখে লাগে। দ্বিতীয় বাধা { __html: … } object: সাধারণ string সরাসরি চলে না, তাই ভুল করে বসানো কঠিন। React-এর docs পরামর্শ দেয়, এই object বানান HTML তৈরির জায়গার যত কাছে সম্ভব, যাতে উৎস নিরাপদ কি না সহজে যাচাই করা যায়। নিরাপত্তায় একে বলে ভুল পথটিকে কঠিন করা।

বাস্তবে এর দরকার পড়ে কম, তবে পড়ে। উদাহরণ: Markdown থেকে HTML-এ বদলানো blog পোস্ট, CMS থেকে আসা লেখা, syntax highlighter-এর আউটপুট, বা JSON-LD-র মতো structured data বসানো। প্রতিটি ক্ষেত্রে প্রশ্ন একই: এই HTML কে লিখেছে, আর পথে কেউ কি তা বদলাতে পারে?

নিরাপদ পথের কয়েকটি স্তর আছে। প্রথমত, সম্ভব হলে HTML এড়িয়ে যান; Markdown-কে সরাসরি React element-এ বদলানোর library আছে। দ্বিতীয়ত, এড়ানো না গেলে পরিচিত sanitizer দিয়ে অনুমোদিত tag-এর তালিকা ধরে পরিষ্কার করুন। নিজে regex লিখে script মুছবেন না; এর ইতিহাস ব্যর্থতায় ভরা। তৃতীয়ত, CSP header রাখুন, যাতে একটি ফাঁক গলে গেলেও inline script না চলে। চতুর্থত, আধুনিক browser-এ Trusted Types আছে, যা DOM-এ এমন জায়গায় শুধু বিশেষভাবে তৈরি মান গ্রহণ করতে বাধ্য করে; React-এর এই prop-ও TrustedHTML মান নিতে পারে।

মূল উপলব্ধি: ভালো API শুধু কাজ করে না, ভুল ব্যবহারকে চোখে পড়ার মতো করে তোলে। Code review-তে dangerously শব্দটি দেখলেই থামুন আর উৎস খুঁজুন।

প্র ০৫React false দেখায় না, কিন্তু 0 দেখায়। এটি কি React-এর নকশার ত্রুটি?

বাগটি এত পরিচিত যে প্রশ্নটি ওঠা স্বাভাবিক। কিন্তু একটু ভাবলে দেখবেন, দুটি যৌক্তিক সিদ্ধান্তের সংঘর্ষ থেকেই এটি এসেছে। প্রথম সিদ্ধান্ত JavaScript-এর: && boolean ফেরত দেয় না, একটি operand ফেরত দেয়। এটি বহু পুরোনো আচরণ, আর ভাষার ওয়েব-সামঞ্জস্যের কারণে বদলানো সম্ভব নয়। দ্বিতীয় সিদ্ধান্ত React-এর: সংখ্যা হলো দেখানোর মতো তথ্য।

React যদি 0 লুকিয়ে ফেলত, তাহলে কী হতো? <p>কার্টে {qty}টি পণ্য</p>-এ qty 0 হলে দেখাত “কার্টে টি পণ্য”। “বাকি: ৳0”, “স্কোর: 0”, “তাপমাত্রা 0°C”, সবই ভাঙত। এই বাগ হতো অনেক বেশি জায়গায়, আর ঠিক করাও হতো কঠিন, কারণ {String(qty)} লিখতে হতো সবখানে। অন্যদিকে এখনকার বাগটি চোখে পড়ে আর সারানো এক শব্দের কাজ। নকশায় প্রায়ই দুটি খারাপের মধ্যে কম খারাপটি বেছে নিতে হয়।

খালি string কিন্তু আগেই কিছু দেখায় না, আর NaN লেখা হয়ে পর্দায় আসে। React-এর নিয়মটি আসলে সরল: “এটি কি লেখায় বদলানোর মতো তথ্য?” true শব্দটি পর্দায় লিখে কারও উপকার হয় না, তাই boolean বাদ। পুরোনো দিনে PHP বা অন্য template ভাষায় true “1” হয়ে ছাপা হওয়ার অভিজ্ঞতা অনেকের আছে।

দলের জন্য সমাধান সাধারণত তিন স্তরের: একটি কোডিং নিয়ম (&&-এর বাম দিকে শুধু boolean), একটি lint নিয়ম (jsx-no-leaked-render), আর একটি test, যেখানে তালিকা খালি বা সংখ্যা শূন্য। TypeScript নিজে থেকে এটি ধরে না, কারণ 0 বৈধ ReactNode; এই পর্বের Shelf উদাহরণে {0} কোনো error ছাড়াই compile হয়েছে।

মূল উপলব্ধি: অনেক “অদ্ভুত” আচরণ আসলে দুটি ঠিক সিদ্ধান্তের মিলনবিন্দু। বাগের জন্য টুলকে দোষ দেওয়ার আগে নিয়মটি বুঝুন, আর সেটিকে lint বা test-এ বদলে দলের অভ্যাসে নিয়ে আসুন।

অনুশীলন

আগে নিজে করুন, তারপর উত্তর মেলান।

  1. কমান্ড চালিয়ে দেখুন: একটি ফাঁকা ফোল্ডারে npm i -D esbuild চালান। Tag.jsx ফাইলে তিনটি লাইন লিখুন: const a = <button />;, const b = <Button />;, const c = <Shop.Item />;. এরপর npx esbuild Tag.jsx --jsx=automatic চালান। চালানোর আগে বলুন, প্রতিটি jsx()-এর প্রথম argument কী হবে।

    সত্যিকারের run (esbuild 0.28.2):

    import { jsx } from "react/jsx-runtime";
    const a = /* @__PURE__ */ jsx("button", {});
    const b = /* @__PURE__ */ jsx(Button, {});
    const c = /* @__PURE__ */ jsx(Shop.Item, {});

    ছোট হাতের নাম string হয়, বড় হাতের নাম ভ্যারিয়েবল হয়, আর ডট-সহ নাম object-এর property হিসেবে পড়া হয়। এই নিয়ম থেকেই বোঝা যায়, ছোট হাতে <button /> লিখলে আপনার নিজের button ফাংশন কখনো ডাকা হবে না।

  2. সংখ্যার হিসাব: একটি পাতায় Shop component-এর ভেতরে ৩০০টি Item, প্রতিটিতে ৫টি DOM element. Shop-এর নিজের আছে ২টি element (এর বাইরে আর কিছু নেই)। Shop-এর state বদলালে শুধু একটি জায়গার সংখ্যা বদলায়। (ক) DOM-এ মোট element কতটি? (খ) একটি update-এ কয়টি component ফাংশন ডাকা হবে, ধরে নিন কোনো memo নেই? (গ) প্রতিটি Item-এ ০.০৪ ms ধরলে (অনুমান), ৬০ Hz-এর বাজেটের কত ভাগ যাবে?

    (ক) ২ + ৩০০ × ৫ = ১৫০২টি element.

    (খ) ১ + ৩০০ = ৩০১টি ফাংশন ডাক। Commit-এ বদল মাত্র একটি জায়গায়।

    (গ) ৩০০ × ০.০৪ = ১২ ms. বাজেট ১৬.৭ ms, তাই ১২ ÷ ১৬.৭ ≈ ৭২%. Layout আর paint-ও ওই সময়ের ভেতরেই হতে হয়, তাই এখানে state-টি নিচের দিকে নামানো বা memo বিবেচনার মতো।

  3. আউটপুট অনুমান করুন: নিচের JSX কী HTML বানাবে? চিন্তা করে লিখুন, তারপর উত্তর মেলান।
    const empty = [];
    <div>{0}{false}{null}{''}{undefined}{[1, 2]}{true}{empty.length && 'x'}</div>

    উত্তর: <div>0120</div>.

    প্রথম 0 দেখায়। false, null, '', undefined কিছু দেখায় না। [1, 2] থেকে আসে 1 আর 2. true কিছু দেখায় না। শেষে empty.length হলো 0, তাই && 0 ফেরত দেয়, আর সেটিই ক্লাসিক বাগের শেষ শূন্য। React 19.3.1-এর সত্যিকারের run-এও এই HTML-ই এসেছে।

  4. নকশা ও সমালোচনা: একজন সহকর্মী বাজারঘরের review-এর জন্য নিচের কোড দিলেন। কয়টি সমস্যা পান, আর কীভাবে লিখবেন?
    function Reviews({ reviews }) {
      return (
        <div class="reviews">
          {reviews.length && <h3>মোট {reviews.length}টি review</h3>}
          {reviews.map(r => (
            <div dangerouslySetInnerHTML={{ __html: r.text }} />
          ))}
          <a href={reviews[0].authorSite}>লেখকের সাইট</a>
        </div>
      );
    }
    • class নয়, className.
    • খালি list-এ reviews.length && পর্দায় 0 দেখাবে; reviews.length > 0 && লিখুন।
    • সবচেয়ে গুরুতর: ব্যবহারকারীর লেখা dangerouslySetInnerHTML-এ। এটি সরাসরি stored XSS-এর পথ। সাধারণ <p>{r.text}</p> লিখলেই escaping কাজ করে।
    • খালি list-এ reviews[0].authorSite চলার সময়ই TypeError দেবে। আর লিংকটি ব্যবহারকারীর দেওয়া, তাই আগে যাচাই করুন যে এটি https:; তা ছাড়া লিংকটি থাকা উচিত প্রতিটি review-এর ভেতরে।
    • map-এ key নেই। পর্ব ১১-তে এর কারণ পাবেন।

    পাঁচটি সমস্যার মধ্যে compile হয়ে যায় বেশিরভাগই। তাই চোখ, lint, test আর খালি ডেটায় চালানো, এই সবই লাগে।

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

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

ক্রমJSX লেখা থেকে পর্দায় আসা পর্যন্ত ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. আপনি .jsx ফাইলে JSX লিখলেন
  2. Build টুল JSX-কে jsx() ডাকে বদলাল
  3. Trigger: root.render() বা set ফাংশন ডাকা হলো
  4. Render: React component ডেকে element object পেল
  5. Commit: React দরকারি DOM বদলাল
  6. Paint: browser পর্দায় পিক্সেল আঁকল

Automatic JSX runtime-এ <h3>চাল</h3> compile হয়ে কী হয়?

  • কোনো বদল হয় না, browser সরাসরি পড়ে
  • jsx("h3", { children: "চাল" })
  • document.createElement("h3")
  • "<h3>চাল</h3>" নামের একটি string

JSX-এর { }-এর ভেতরে কোনটি লেখা যায় না?

  • {qty > 0 ? 'আছে' : 'নেই'}
  • {items.map(f)}
  • {if (qty > 0) { 'আছে' }}
  • {qty * 120}

React-এ stock-এর মান 0. <p>{stock && <b>আছে</b>}</p> কী দেখাবে?

  • “আছে” মোটা অক্ষরে
  • খালি <p></p>
  • Error: Objects are not valid as a React child
  • <p>0</p>

একটি ক্লিকে setQty(2) ডাকা হলো। React-এর render ধাপে কী ঘটে?

  • React component ফাংশন ডেকে নতুন element পায়, DOM তখনো বদলায় না
  • React সরাসরি DOM-এর লেখা বদলে দেয়
  • Browser পর্দায় পিক্সেল আঁকে
  • Browser পুরো পাতা reload করে

একটি review-এর লেখা <b>ভালো</b>. React-এ <p>{review}</p> দিয়ে দেখালে কী হবে?

  • “ভালো” মোটা অক্ষরে দেখাবে
  • পুরো লেখাটি <b>-সহ সাধারণ লেখা হিসেবে দেখাবে
  • React error দেবে
  • Tag মুছে শুধু “ভালো” দেখাবে

React-এ কোন ঘটনা নিজে থেকে একটি component-এর re-render ঘটায় না?

  • নিজের state-এ নতুন মান দিয়ে set ফাংশন ডাকা
  • Parent component-এর re-render
  • Component-এর ভেতরে let count ভ্যারিয়েবল বাড়ানো
  • প্রথমবার root.render() ডাকা

শব্দকোষ

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

মনে রাখুন

  1. JSX মানে ফাংশন ডাক: build-এ jsx() হয়, চলার সময় একটি সাধারণ element object বানায়।
  2. {}-এ শুধু expression চলে; attribute camelCase: className, htmlFor, onClick.
  3. true, false, null, undefined কিছু দেখায় না, কিন্তু 0 দেখায়; &&-এর বাম দিকে boolean রাখুন।
  4. JSX লেখাকে escape করে; dangerouslySetInnerHTML-এ এই সুরক্ষা নেই, ব্যবহারকারীর লেখা সেখানে নয়।
  5. Trigger → Render → Commit: render মানে ফাংশন ডাকা, DOM বদলায় commit-এ; re-render মানেই DOM বদল নয়।

পরের পর্বে: এই পর্বে children আর props উঁকি দিয়েছে। পর্ব ০৯-এ আমরা বাজারঘরের পাতা ছোট ছোট component জোড়া দিয়ে বানাব, আর দেখব props আর composition দিয়ে কীভাবে একই component নানা জায়গায় কাজে লাগে।

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

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