Skip to content

JSX and Component Rendering ​

JSX is syntax for describing a tree of elements inside JavaScript. It resembles HTML, but a browser does not execute JSX directly. A build transform converts it into JavaScript calls understood by a UI library.

jsx
function Greeting({ name }) {
  return <h1>Hello, {name}</h1>;
}

With a modern React transform, the result is conceptually similar to:

js
function Greeting({ name }) {
  return jsx("h1", { children: ["Hello, ", name] });
}

The exact helper is an implementation detail. The essential point is that <h1> produces a JavaScript description, not a DOM node. React later reconciles that description with a renderer such as the browser DOM.

TSX is JSX in a TypeScript file. It adds checking of component props, intrinsic elements, and expressions; it does not change React's runtime behavior.

Markup and JavaScript expressions coexist ​

Curly braces enter JavaScript expression mode:

jsx
function Transaction({ transaction }) {
  return (
    <article className={transaction.settled ? "settled" : "pending"}>
      <h2>{transaction.description}</h2>
      <p>{formatCurrency(transaction.amount)}</p>
    </article>
  );
}

JSX attributes use JavaScript-oriented names for some DOM properties: className, htmlFor, and camel-cased event handlers such as onClick. Most attributes closely resemble HTML, but JSX follows the library's property model rather than being pasted HTML.

JSX expressions must produce one enclosing value. A fragment groups siblings without adding a DOM wrapper:

jsx
return (
  <>
    <Header />
    <TransactionList />
  </>
);

Lowercase tags describe host elements such as div; capitalized names refer to variables, normally components:

jsx
const heading = <h1>Report</h1>;
const page = <ReportPage report={report} />;

Components are ordinary JavaScript functions by convention, but React controls when they are called. Call them with JSX rather than ReportPage({ report }), so hooks and component identity remain under React's rules.

Rendering collections requires identity ​

jsx
function TransactionList({ transactions }) {
  return (
    <ul>
      {transactions.map(transaction => (
        <li key={transaction.id}>
          {transaction.description}
        </li>
      ))}
    </ul>
  );
}

key helps React associate an element with the same logical item across insertions, removals, and reordering. It is consumed by React and is not passed as an ordinary prop. Use a stable identity from the data. An array index is safe only when item identity truly follows position and the list does not reorder or change shape.

Conditional rendering is JavaScript ​

jsx
function ReportBody({ state }) {
  if (state.status === "loading") return <Spinner />;
  if (state.status === "failure") return <ErrorMessage error={state.error} />;

  return state.transactions.length === 0
    ? <EmptyState />
    : <TransactionList transactions={state.transactions} />;
}

Inside JSX, short-circuiting is common:

jsx
{isAdmin && <AdminControls />}

Remember that && returns an operand. If the left value is 0, React can render 0:

jsx
{items.length > 0 && <List items={items} />}

Explicit booleans avoid accidental output.

Props are inputs; composition supplies structure ​

jsx
function Panel({ title, children }) {
  return (
    <section className="panel">
      <h2>{title}</h2>
      {children}
    </section>
  );
}

<Panel title="Summary">
  <ReportSummary report={report} />
</Panel>

children is an ordinary prop populated by nested JSX. Component composition often replaces inheritance: a parent supplies content or specialized components through props.

Keep components pure during rendering. Given the same props, state, and context, a render should calculate the same description without changing outside state:

jsx
// Bad: mutation during rendering
function Total({ transactions }) {
  analytics.record("total rendered");
  return <span>{calculateTotal(transactions)}</span>;
}

Rendering may be repeated, paused, or discarded. Event handlers and effects are the places for external interaction.

JSX escaping is helpful but not universal sanitization ​

React escapes string values inserted with braces, so user text is normally rendered as text:

jsx
<p>{comment.body}</p>

dangerouslySetInnerHTML bypasses that protection and should receive only trusted or correctly sanitized HTML. JSX also cannot make an unsafe URL, server request, or authorization decision safe; security depends on the specific sink and boundary.

Rendering is separate from component description ​

A browser entry point mounts a React tree into an existing DOM node:

jsx
import { createRoot } from "react-dom/client";
import { App } from "./App.jsx";

const rootElement = document.querySelector("#root");
if (!rootElement) throw new Error("Missing #root element");

createRoot(rootElement).render(<App />);

React DOM is one renderer. Server rendering produces HTML; React Native targets native interfaces. JSX itself does not imply the DOM, React, state, or interactivity. It is a syntax layer whose meaning comes from the configured transform and runtime.

Identity, accessibility, and hydration remain visible ​

Changing a component's type or key at a position tells React the previous identity ended, so its local state is discarded. Keeping the same type and key retains state at that position. Keys represent domain identity; they are not merely warning suppressors.

JSX does not replace HTML semantics:

jsx
<form role="search" onSubmit={onSubmit}>
  <label htmlFor="query">Search the catalog</label>
  <input id="query" name="query" type="search" />
  <button type="submit">Search</button>
</form>

Type checking can catch misspelled props, but it cannot prove usable focus order, accessible names, announcements, or keyboard behavior.

JSX can also be rendered to HTML on a server and hydrated in the browser. Hydration expects the first client render to describe compatible markup. Random values, local time, or browser storage read during render can make the server and client disagree. Pass stable serialized initial data or defer browser-owned information to an effect when replacement after mount is acceptable.

Optional prompts ​

Predict: What appears for {items.length && <List items={items} />} when items is empty, and what expression avoids the surprise?

Answer: items.length is 0; && returns that operand, and React can render the number 0. Use items.length > 0 && <List ... /> or an explicit conditional so the left side is a boolean rather than displayable data.

Explain: Why is <button>Save</button> a description rather than a browser DOM node at the moment the JSX expression runs?

Answer: The JSX transform creates a JavaScript element value describing type, props, and children. React's renderer later reconciles that description with a target such as the DOM, server HTML, or a native renderer. This separation is why JSX itself does not imply a browser.

Debug: A reorderable list uses array indexes as keys, and an edited row's local state moves to another item after sorting. What caused the bug?

Answer: React used each position as identity, so state remained with the index while domain items moved. Use a stable identifier from the item, such as transaction.id, so reconciliation can associate each component and its state with the same logical entity.

Further reference ​