JSX Lists, Keys, and Conditionals

Introduction

Real UIs show lists of items, conditional content, and dynamic attributes. This chapter covers map() rendering, stable key props, conditional patterns in JSX, event handlers, and the XSS risks of dangerouslySetInnerHTML.

Prerequisites

Rendering Lists

Code explanation:

  • {todos.map(...)} embeds an array of elements in JSX
  • Each child in a list needs a key so React can match items across re-renders
  • Use a stable unique id—not array index when items can reorder or delete

Warning

Do Not Use Index as Key When Order Changes

Using key={index} breaks state and animations when you insert, remove, or sort items. Prefer item.id.

Compare with Vue v-for and :key.

Keys in Practice

SituationKey choice
Database recordsPrimary key / uuid
Static demo listGenerated id once
Wrongkey={index} when list is mutable

Keys are not passed to the component as props—they are hints for React’s reconciler.

Conditional Rendering

Logical AND

tsx
{isLoggedIn && <p>Welcome back!</p>}

Falsy values (0, "") can render unexpectedly—prefer explicit checks for numbers:

tsx
{count > 0 && <span>{count} items</span>}

Ternary

tsx
{loading ? <p>Loading…</p> : <PostList posts={posts} />}

Early return

tsx
function Profile({ user }: { user: User | null }) {
  if (!user) {
    return <p>Please sign in.</p>;
  }
 
  return <h1>{user.name}</h1>;
}

if statements cannot sit inside JSX {}—compute above return or use ternary/&&.

Dynamic Attributes

tsx
<img src={avatarUrl} alt={user.name} width={48} height={48} loading="lazy" />
 
<a href={profilePath} className={isActive ? "nav active" : "nav"}>
  Profile
</a>

Spread props:

tsx
function InputField(props: React.InputHTMLAttributes<HTMLInputElement>) {
  return <input {...props} />;
}

Event Handlers

tsx
function Item({ id, onDelete }: { id: string; onDelete: (id: string) => void }) {
  return (
    <button type="button" onClick={() => onDelete(id)}>
      Delete
    </button>
  );
}

Code explanation:

  • Pass () => onDelete(id) when the handler needs arguments
  • onClick={onDelete} only when the signature matches the event

See JavaScript events for bubbling and preventDefault.

dangerouslySetInnerHTML

tsx
// Avoid for user-generated content
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />

Warning

XSS Risk

Never pass raw user input to dangerouslySetInnerHTML. Prefer text interpolation {text}, which escapes content. Same caution as Vue v-html.

Filtering Before map

tsx
const visible = todos.filter((t) => !t.done);
 
return (
  <ul>
    {visible.map((todo) => (
      <li key={todo.id}>{todo.text}</li>
    ))}
  </ul>
);

For large lists, compute filtered data in the parent or with useMemoPerformance.

FAQ

Missing key warning?

Add key on the outermost element returned from map.

Empty list UI?

tsx
{todos.length === 0 ? <p>No todos yet.</p> : <ul>...</ul>}

Can I use for loops in JSX?

Use map. A for loop before return is fine to build an array of elements.

Fragment in lists?

tsx
{todos.map((t) => (
  <Fragment key={t.id}>
    <dt>{t.text}</dt>
    <dd>{t.done ? "Done" : "Open"}</dd>
  </Fragment>
))}

Import Fragment from react.

Next chapter?

State: useState and useReducer.