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
- First React Application and JSX
- Familiarity with JavaScript arrays and array methods (adapt concept from Python lists if needed)
Rendering Lists
Code explanation:
{todos.map(...)}embeds an array of elements in JSX- Each child in a list needs a
keyso 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
| Situation | Key choice |
|---|---|
| Database records | Primary key / uuid |
| Static demo list | Generated id once |
| Wrong | key={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
{isLoggedIn && <p>Welcome back!</p>}Falsy values (0, "") can render unexpectedly—prefer explicit checks for numbers:
{count > 0 && <span>{count} items</span>}Ternary
{loading ? <p>Loading…</p> : <PostList posts={posts} />}Early return
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
<img src={avatarUrl} alt={user.name} width={48} height={48} loading="lazy" />
<a href={profilePath} className={isActive ? "nav active" : "nav"}>
Profile
</a>Spread props:
function InputField(props: React.InputHTMLAttributes<HTMLInputElement>) {
return <input {...props} />;
}Event Handlers
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
// 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
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 useMemo—Performance.
FAQ
Missing key warning?
Add key on the outermost element returned from map.
Empty list UI?
{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?
{todos.map((t) => (
<Fragment key={t.id}>
<dt>{t.text}</dt>
<dd>{t.done ? "Done" : "Open"}</dd>
</Fragment>
))}Import Fragment from react.