Skip to content

JSX and Rendering Interview Questions & Answers

16 questions Updated 2026-06-23 Share:

React JSX interview questions — JSX syntax, transpilation, fragments, expressions, boolean attributes, and the differences between JSX and HTML.

Read the in-depth guideReact JSX — A Complete Guide with Examples(opens in new tab)
16 of 16

JSX is a syntax extension to JavaScript that lets you write HTML-like markup inside a .js or .tsx file. It is not valid JavaScript — a build tool (Babel, SWC, or the TypeScript compiler) transforms every JSX expression into a React.createElement call before the browser sees it.

// What you write
const el = <h1 className="title">Hello</h1>

// What the compiler produces (React 17+ JSX transform uses _jsx instead)
const el = React.createElement('h1', { className: 'title' }, 'Hello')

The output is a plain JavaScript object — a React element — that describes what should appear on screen. React's renderer later turns that description into actual DOM nodes.

Rule of thumb: JSX is syntactic sugar for React.createElement. If you can't picture the compiled form, you don't yet fully understand what JSX is.

Because JSX compiles to a single React.createElement call, which returns one value. You can't return two values from one expression in JavaScript.

// ❌ Invalid — two adjacent elements at the top level
return (
  <h1>Title</h1>
  <p>Body</p>
)

// ✅ Wrap in a div
return (
  <div>
    <h1>Title</h1>
    <p>Body</p>
  </div>
)

// ✅ Or use a Fragment (no extra DOM node)
return (
  <>
    <h1>Title</h1>
    <p>Body</p>
  </>
)

Rule of thumb: when you need siblings without a wrapper DOM node, reach for a Fragment (<>…</> or <React.Fragment>…</React.Fragment>).

A Fragment groups children without adding an extra node to the DOM. There are two syntaxes:

// Short syntax — most common
return (
  <>
    <dt>Term</dt>
    <dd>Definition</dd>
  </>
)

// Long syntax — required when you need the key prop (e.g. in a list)
return items.map(item => (
  <React.Fragment key={item.id}>
    <dt>{item.term}</dt>
    <dd>{item.def}</dd>
  </React.Fragment>
))

Use Fragments when:

  • A wrapper <div> would break CSS layout (e.g. inside a <table> or flex/grid container).
  • You're mapping over pairs of sibling elements that each need a key.

Rule of thumb: prefer <>…</> by default; switch to the named form only when you need to attach key.

Wrap any JavaScript expression in {}. An expression is anything that produces a single value — a variable, function call, ternary, template literal, etc.

const name = 'Alice'
const isAdmin = true

return (
  <div>
    <p>Hello, {name.toUpperCase()}</p>
    <p>Role: {isAdmin ? 'Admin' : 'User'}</p>
    <p>Sum: {2 + 2}</p>
  </div>
)

What you cannot put in {}:

  • Statements (if, for, variable declarations) — they don't produce a value. Extract them above the return, or use expressions like ternaries and Array.map.
  • Objects as children: {myObject} will throw; use JSON.stringify or extract individual properties.

Rule of thumb: if it can go on the right-hand side of =, it can go inside {}.

Several attribute names and rules differ:

HTML JSX Reason
class className class is a reserved JS keyword
for htmlFor for is a reserved JS keyword
onclick onClick Events use camelCase in JSX
<br> <br /> All tags must be closed in JSX
style="color:red" style={{ color: 'red' }} Style takes a JS object
tabindex tabIndex camelCase for multi-word attributes
// HTML
// <label class="label" for="email" onclick="submit()">Email</label>

// JSX
<label className="label" htmlFor="email" onClick={submit}>Email</label>

Additionally, JSX is case-sensitive: <div> is a DOM element; <Div> would be treated as a custom component.

Rule of thumb: when an HTML attribute doesn't work in JSX, check if it's a reserved keyword or needs camelCase.

In JSX, omitting a value for an attribute is the same as setting it to true. You never write disabled="true" — that's an HTML string, not a boolean.

// These are equivalent
<input disabled={true} />
<input disabled />

// Explicitly false — attribute is omitted from the DOM
<input disabled={false} />

// Dynamic
<button disabled={isLoading}>Submit</button>

A common mistake is disabled="false" — HTML treats any non-empty string as truthy, so the button would still be disabled. Always use {false}.

Rule of thumb: use {true} / {false} for any attribute that has a boolean semantic; never pass boolean values as strings.

JSX uses the case of the first letter to decide how to compile the tag:

  • Lowercase (<div>, <span>) → React.createElement('div', …) — passed as a string, rendered as a native DOM element.
  • Uppercase (<MyComponent>) → React.createElement(MyComponent, …) — passed as a variable reference, rendered as a React component.
// React treats 'button' as a DOM element — works as expected
const el = <button onClick={handle}>Click</button>

// React looks up `button` as a variable — ReferenceError if not defined
const el = <button />   // ← lowercase: treated as DOM tag, fine
const el = <Button />   // ← uppercase: looks for a `Button` variable

If you store a component in a lowercase variable, you must reassign it to an uppercase one before using it in JSX:

const components = { circle: CircleIcon }
const Icon = components['circle']   // uppercase alias required
return <Icon />

Rule of thumb: always capitalize component names; use lowercase only for HTML intrinsic elements.

The style prop takes a JavaScript object, not a CSS string. Property names are camelCase versions of their CSS counterparts.

// ❌ HTML-style string — not valid in JSX
<div style="color: red; font-size: 16px">…</div>

// ✅ JSX object — note the double braces: outer {} for expression,
//    inner {} for the object literal
<div style={{ color: 'red', fontSize: '16px', marginTop: 8 }}>…</div>

Values that are numbers (like marginTop: 8) are automatically converted to pixels for properties that accept pixel units. Values for others (like opacity: 0.5) are used as-is.

Rule of thumb: for anything beyond a quick prototype, prefer CSS modules or a utility class over inline styles — they don't support media queries or pseudo-selectors.

Comments inside JSX must be wrapped in {} and use the /* … */ syntax — not //, which would comment out the closing } as well.

return (
  <div>
    {/* This is a JSX comment — it won't appear in the rendered output */}
    <p>Visible text</p>
    {/* <p>Commented-out element</p> */}
  </div>
)

Outside of JSX (above the return), normal JS comments work fine.

Rule of thumb: {/* comment */} inside JSX, // comment outside.

Use the spread syntax {...obj} to forward a set of props without listing each one individually.

const buttonProps = { type: 'button', disabled: false, 'aria-label': 'Save' }
return <button {...buttonProps}>Save</button>

// Equivalent to:
return <button type="button" disabled={false} aria-label="Save">Save</button>

A common pattern in wrapper components:

function IconButton({ icon, children, ...rest }) {
  // `rest` captures all props not explicitly destructured
  return (
    <button {...rest}>
      <Icon name={icon} />
      {children}
    </button>
  )
}

Rule of thumb: spread props enable flexible pass-through but make it easy to accidentally forward unknown props to DOM elements (which triggers React warnings). Destructure known props and spread only the remainder.

Returning null renders nothing — the component mounts (lifecycle runs, refs attach) but contributes no DOM nodes. It's the idiomatic way to conditionally hide output without unmounting.

function Banner({ show, message }) {
  if (!show) return null
  return <div className="banner">{message}</div>
}

Crucially, returning null is different from not rendering the component at all:

  • null → component mounts; useEffect and refs still fire.
  • Conditional rendering ({show && <Banner />}) → component is not mounted when show is false; effects and state are destroyed.

Rule of thumb: return null when you want the component in the tree (for effects or imperative refs) but invisible; use conditional rendering when you want it fully gone.

React can render an array of React elements as a child. The standard approach is Array.map:

const fruits = ['Apple', 'Banana', 'Cherry']

return (
  <ul>
    {fruits.map(fruit => (
      <li key={fruit}>{fruit}</li>   // key is required on the outermost element
    ))}
  </ul>
)

React also accepts arrays returned directly:

function Tags({ tags }) {
  return tags.map(tag => <span key={tag} className="tag">{tag}</span>)
}

Things that React will not render as children: plain objects, undefined (silently skipped), and false (silently skipped — useful for short-circuits).

Rule of thumb: always add a stable key to each element in a mapped list; without it, React warns and may produce incorrect diffs.

Single {} opens a JavaScript expression slot in JSX. Double {{}} is simply that expression slot containing an object literal — there's no special double-brace syntax.

// {} — expression slot
<p>{count}</p>

// {{}} — expression slot containing an object literal
<div style={{ color: 'red', fontSize: 16 }}>…</div>
//          ^outer: JSX expression  ^inner: JS object

The confusion usually arises with style because it's the most common attribute that takes an object value.

Rule of thumb: think of it as two separate things: "open expression" then "open object". The outer {} is JSX; the inner {} is JavaScript.

In JSX, every element must be explicitly closed — either with a closing tag or with /> for self-closing. This is stricter than HTML, where void elements like <br> and <img> don't require closing.

// ❌ HTML-style void element — invalid in JSX
<input type="text">
<img src="photo.jpg">

// ✅ Self-close when there are no children
<input type="text" />
<img src="photo.jpg" />

// ✅ Or use a closing tag (unusual for void elements, but valid JSX)
<input type="text"></input>

Custom components can also be self-closed when you pass no children: <MyWidget />.

Rule of thumb: if an element has no children, self-close it with />; never leave a tag unclosed in JSX.

Anything placed between opening and closing JSX tags is automatically passed as the children prop.

function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="body">{children}</div>
    </div>
  )
}

// Usage — the <p> and <button> become children
<Card title="Welcome">
  <p>Hello there!</p>
  <button>OK</button>
</Card>

children can be a string, a single element, an array of elements, or even a function (render prop pattern). Use React.Children utilities if you need to iterate or count children programmatically.

Rule of thumb: children is just a prop; you can also pass it explicitly as <Card children={<p>Hi</p>} />, though the nested form is more readable.

No — if is a statement, not an expression, so it can't go inside {}. JSX only accepts expressions.

// ❌ if statement inside JSX — syntax error
return (
  <div>
    {if (isLoggedIn) { return <Dashboard /> }}
  </div>
)

// ✅ Ternary — an expression
return (
  <div>
    {isLoggedIn ? <Dashboard /> : <Login />}
  </div>
)

// ✅ Logical && — renders right side only if left is truthy
return (
  <div>
    {isAdmin && <AdminPanel />}
  </div>
)

// ✅ If/else above the return — perfectly fine
let content
if (isLoggedIn) { content = <Dashboard /> }
else { content = <Login /> }
return <div>{content}</div>

Rule of thumb: for simple two-path branches use a ternary; for one-side rendering use &&; for complex logic extract it above the return.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel