RizTech Academy logo
RizTech Academy
React FundamentalsLesson 1 of 725 min

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

  1. Create apps/web/src/components/product-card.tsx with a hard-coded card. Render it three times on the home page.
  2. Lowercase the component name and see it disappear.
  3. Return two sibling elements without a wrapper. Read the error, then fix it with a fragment.
  4. Store a price in paise and render it as rupees to two decimal places.
  5. Add a stock boolean. Render a button when in stock and "Out of stock" otherwise.
  6. Add reviewCount = 0 and render it with &&. Find the 0 on the page, then fix it.
  7. Set disabled="false" on a button and observe it is disabled. Fix it.
  8. 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