Components and JSX
React builds interfaces from components: functions that return a description of what should appear on screen. That is genuinely the whole idea, and everything else is consequence.
A component
export function ProductCard() {
return (
<article className="rounded-xl border p-4">
<h2 className="font-semibold">Aashirvaad Atta 5kg</h2>
<p className="text-gray-600">₹285.00</p>
<button>Add to cart</button>
</article>
);
}
A function whose name begins with a capital letter, returning something that looks like HTML.
The capital letter is not style. React uses it to tell your components apart
from built-in elements. <productCard /> is treated as an unknown HTML tag and
renders nothing; <ProductCard /> is your function. When a component
mysteriously produces no output, check the capital first.
Using it:
export function Page() {
return (
<main>
<h1>Today's offers</h1>
<ProductCard />
<ProductCard />
</main>
);
}
Components compose like functions, because they are functions.
JSX is not HTML
That HTML-looking syntax is JSX, and it compiles to function calls:
<h2 className="font-semibold">Atta</h2>
becomes roughly:
React.createElement("h2", { className: "font-semibold" }, "Atta");
You never write that, and knowing it exists explains the rules.
className, not class. class is a reserved word in JavaScript. Same for
htmlFor instead of for.
camelCase attributes. onClick, tabIndex, maxLength — because these are
JavaScript object keys, not HTML attributes.
Every tag must close. <img />, <br />, <input />. HTML forgives this;
JSX does not.
One root element. A component returns one thing:
// Error: JSX expressions must have one parent element
return (
<h2>Atta</h2>
<p>₹285</p>
);
Wrap it, or use a fragment when you do not want an extra <div> in the output:
return (
<>
<h2>Atta</h2>
<p>₹285</p>
</>
);
<>...</> is a fragment — grouping with no rendered element. Useful inside
table rows and flex layouts, where a stray <div> breaks the layout.
Embedding values
Curly braces run JavaScript:
export function ProductCard() {
const name = "Aashirvaad Atta 5kg";
const pricePaise = 28500;
return (
<article>
<h2>{name}</h2>
<p>₹{(pricePaise / 100).toFixed(2)}</p>
<p>{name.length} characters</p>
</article>
);
}
Anything inside { } is an expression — it must produce a value. So if
statements and for loops do not go there. Conditional expressions and .map()
do, which is why React code uses them constantly.
Attributes take expressions too:
<img src={product.imageUrl} alt={product.name} />
<button disabled={!product.inStock}>Add to cart</button>
Note disabled={!product.inStock} with no quotes. disabled="false" would be
the string "false", which is truthy, so the button would be disabled. A
genuine bug, and a common one.
Conditional rendering
No if inside JSX, so:
{product.inStock ? (
<button>Add to cart</button>
) : (
<span className="text-gray-500">Out of stock</span>
)}
For "show this or nothing":
{product.isNew && <span className="badge">New</span>}
&& short-circuits — false means nothing renders.
The && trap
{product.reviewCount && <p>{product.reviewCount} reviews</p>}
With zero reviews, that renders 0 on the page. 0 is falsy, so &&
returns 0, and React renders the number 0 rather than nothing.
This is module 2 of the Python course all over again — a falsy value that is a legitimate result. The fix is an explicit comparison:
{product.reviewCount > 0 && <p>{product.reviewCount} reviews</p>}
Never put a number on the left of && in JSX. Once you have seen a stray
0 in an interface, you recognise it everywhere.
null, undefined, false and "" all render nothing, which is why they are
safe.
An if before the return
For anything more than a small condition, decide before returning:
export function ProductCard({ product }: { product: Product | null }) {
if (!product) {
return <p className="text-gray-500">Product not found.</p>;
}
return (
<article>
<h2>{product.name}</h2>
</article>
);
}
Guard clauses, exactly as in any other function. Far more readable than nesting conditionals inside JSX.
Styling
This course uses Tailwind, which is classes rather than separate CSS files:
<button className="rounded-lg bg-emerald-600 px-4 py-2 text-white hover:bg-emerald-700">
Add to cart
</button>
It looks noisy at first and has a real advantage: the styles are next to the markup, so deleting a component deletes its styles. Module 4 covers it properly.
Inline styles exist and take an object:
<div style={{ width: "50%" }} />
Two braces — one for JSX, one for the object. Use sparingly, for values computed at runtime.
Comments
{/* This is a JSX comment */}
Curly braces around a JavaScript comment, because plain // inside JSX renders
as text.
Check your work
Why the capital letter: React treats a lowercase tag as an HTML element, so
<productCard /> renders nothing.
Why two siblings fail: a component returns one thing. Wrap them, or use a
fragment <>...</> when you do not want an extra element in the output.
Rendering paise as rupees: ₹{(pricePaise / 100).toFixed(2)}.
Why reviewCount && ... renders 0: 0 is falsy, so && returns 0, and
React renders the number. Fix it with reviewCount > 0 && ....
Why disabled="false" disables the button: it is the non-empty string
"false", which is truthy. Use disabled={false}.
Why a guard clause beats nesting: the early return keeps the main JSX flat and each case is one line.
Practice
- Create
apps/web/src/components/product-card.tsxwith a hard-coded card. Render it three times on the home page. - Lowercase the component name and see it disappear.
- Return two sibling elements without a wrapper. Read the error, then fix it with a fragment.
- Store a price in paise and render it as rupees to two decimal places.
- Add a stock boolean. Render a button when in stock and "Out of stock" otherwise.
- Add
reviewCount = 0and render it with&&. Find the0on the page, then fix it. - Set
disabled="false"on a button and observe it is disabled. Fix it. - Add a guard clause returning early for a missing product.
Next: making the card work for any product, rather than one.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship