Rendering is how React turns your component tree into the user interface visible in the browser. Understanding how React renders elements โ when a component re-renders, how conditional rendering controls what is shown, and how the map method turns data arrays into lists of elements โ are core skills you will use in every component you write in the MERN Blog client. This lesson covers all three patterns with practical examples from the blog domain.
How React Renders
Initial render:
1. React calls your component function
2. The function returns JSX
3. React creates DOM nodes from the JSX
4. React inserts them into the page at the <div id="root"> mount point
Re-render (after state or prop change):
1. React calls your component function again with the new state/props
2. The function returns new JSX
3. React diffs the new virtual DOM against the previous one
4. React applies only the changed DOM nodes โ not a full page refresh
When does a component re-render?
โ Its own state changes (useState setter is called)
โ A prop passed to it changes
โ Its parent re-renders (unless memoised with React.memo)
โ A variable inside the component changes (not tracked by React)
useEffect โ not in the function body. When a component re-renders, React may call it multiple times (especially in StrictMode) to detect side effects โ pure rendering functions are safe to call multiple times.<PostListSection posts={posts} isLoading={loading} error={error} /> and put the conditional logic there. This keeps your page component readable and makes each piece independently testable..map(). Without it, React cannot efficiently reconcile the list when items are added, removed, or reordered โ it will either produce a console warning or, worse, silently update the wrong element. The key must be stable (same value across re-renders) and unique among siblings. Always use a database ID like post._id โ never use the array index as a key for dynamic lists.Conditional Rendering โ Three Patterns
// โโ Pattern 1: if/else before the return โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function PostPage({ postId }) {
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// ... useEffect to load post ...
// Use if/else for complex multi-branch conditions BEFORE the return
if (loading) return <div className="spinner">Loading...</div>;
if (error) return <div className="error">Error: {error}</div>;
if (!post) return <div className="not-found">Post not found</div>;
// Only reaches here if post is loaded and valid
return <article><h1>{post.title}</h1></article>;
}
// โโ Pattern 2: Ternary operator โ inside JSX for two-branch conditions โโโโโโโโโ
function PostCard({ post }) {
return (
<article>
<h2>{post.title}</h2>
{/* Ternary: show one thing or another */}
{post.published
? <span className="badge badge--published">Published</span>
: <span className="badge badge--draft">Draft</span>
}
{/* Nested ternary โ keep shallow, max 1โ2 levels */}
<p>{post.viewCount > 1000 ? 'Trending' : post.viewCount > 100 ? 'Popular' : 'New'}</p>
</article>
);
}
// โโ Pattern 3: Logical AND โ show or show nothing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function PostCard({ post, isOwner }) {
return (
<article>
<h2>{post.title}</h2>
{/* Render only if condition is truthy */}
{post.featured && <span className="badge badge--featured">โญ Featured</span>}
{/* Only show edit/delete to the post owner */}
{isOwner && (
<div className="post-actions">
<button>Edit</button>
<button>Delete</button>
</div>
)}
</article>
);
}
Rendering Lists with map()
// โโ Basic list rendering โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function TagList({ tags }) {
return (
<ul className="tag-list">
{tags.map(tag => (
<li key={tag} className="tag">
#{tag}
</li>
))}
</ul>
);
}
// โโ List of objects with components โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function PostList({ posts }) {
if (posts.length === 0) {
return <p className="empty-state">No posts found.</p>;
}
return (
<div className="post-list">
{posts.map(post => (
// key uses post._id from MongoDB โ stable and unique
<PostCard key={post._id} post={post} />
))}
</div>
);
}
// โโ Filtered list โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function PublishedPosts({ posts }) {
const published = posts.filter(p => p.published);
return (
<ul>
{published.map(post => (
<li key={post._id}>{post.title}</li>
))}
</ul>
);
}
// โโ Sorted list with conditional class โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function SortedPostList({ posts }) {
const sorted = [...posts].sort((a, b) => b.viewCount - a.viewCount);
return (
<ol className="ranked-list">
{sorted.map((post, index) => (
<li
key={post._id}
className={index === 0 ? 'post-item post-item--top' : 'post-item'}
>
#{index + 1} โ {post.title} ({post.viewCount} views)
</li>
))}
</ol>
);
}
The Key Prop โ Why It Matters
Without key โ React cannot track which list item is which:
Posts: [A, B, C]
After delete B: [A, C]
React sees: "list went from 3 to 2 items โ update item 2 to C's data,
remove item 3" โ potentially re-renders the WRONG component
With key={post._id} โ React tracks identity:
Posts: [{ _id: 1, title: A }, { _id: 2, title: B }, { _id: 3, title: C }]
After delete _id:2: [{ _id: 1, title: A }, { _id: 3, title: C }]
React sees: "item with key=2 was removed โ keep 1 and 3 as-is"
โ Correct, efficient DOM update
Why NOT to use array index as key:
posts.map((post, index) => <PostCard key={index} ... />)
If posts are sorted or filtered, the indices change โ React thinks
different items are the same item โ state bugs and stale UI
Common Mistakes
Mistake 1 โ Using array index as key for dynamic lists
โ Wrong โ index-based key causes bugs when items are reordered or filtered:
{posts.map((post, index) => <PostCard key={index} post={post} />)}
// If posts are sorted or a post is deleted โ keys shift โ React updates wrong components
โ Correct โ use the stable unique ID from MongoDB:
{posts.map(post => <PostCard key={post._id} post={post} />)} // โ
Mistake 2 โ Using 0 as a falsy value in logical AND rendering
โ Wrong โ 0 is falsy and renders as “0” in JSX, not as nothing:
{posts.length && <PostList posts={posts} />}
// If posts.length === 0 โ renders "0" in the DOM instead of nothing!
โ Correct โ use a boolean expression:
{posts.length > 0 && <PostList posts={posts} />} // โ
// or:
{!!posts.length && <PostList posts={posts} />} // โ
Mistake 3 โ Forgetting to handle the empty list case
โ Wrong โ rendering nothing when the list is empty, leaving the user confused:
{posts.map(post => <PostCard key={post._id} post={post} />)}
// When posts = [] โ renders nothing โ user sees a blank page with no explanation
โ Correct โ always handle the empty state:
{posts.length === 0
? <p className="empty">No posts yet. Be the first to write one!</p>
: posts.map(post => <PostCard key={post._id} post={post} />)
}
Quick Reference
| Pattern | When to Use | Code |
|---|---|---|
| if before return | Loading/error/not-found guards | if (loading) return <Spinner /> |
| Ternary | Show A or show B | {cond ? <A /> : <B />} |
| Logical AND | Show or show nothing | {isOwner && <EditButton />} |
| Array.map() | Render a list from data | {posts.map(p => <PostCard key={p._id} post={p} />)} |
| Empty state | No items in list | {items.length === 0 && <p>No items</p>} |
| Stable key | List item identity | key={item._id} (from database) |