When loading finishes successfully but there's genuinely no data, rendering nothing looks like a bug. This lesson covers why different reasons for emptiness need different messaging, the three-part anatomy of a good empty state, and the correct order of loading/error/empty checks.
1Loading Finished. There's Just Nothing There.
When isLoading is false, there's no error, and the underlying data is simply an empty array, rendering nothing — or a bare, empty container — looks exactly like a bug to users, even though the operation actually completed successfully.
2Not All Empty States Are the Same Empty State
Having zero orders ever and having zero search results are both empty lists, but they call for entirely different messaging and next actions. A single generic 'No data' message for every empty case misses the opportunity to genuinely help the user in each specific situation.
3The Anatomy of a Good Empty State
A well-designed empty state has three parts: a clear explanation of why it's empty, an optional supporting visual like an icon or illustration, and, most importantly, a clear next action the user can actually take, such as a 'Create your first project' button.
4Distinguishing Empty from Loading from Error
Loading, error, and empty are three genuinely distinct states requiring checks in a specific order: loading first, then error, and only once real, confirmed data exists, checking whether that data is empty. Checking emptiness too early can misleadingly show an empty state while data is still actually loading.
5Step-by-Step Breakdown
Loading Finished. There's Just Nothing There.. isLoading is false, there's no error, and the array is simply empty — a brand-new user with zero orders, a search with zero matches. Rendering nothing (or a bare, empty list container) in this case looks exactly like a bug, even though everything technically worked correctly.
Not All Empty States Are the Same Empty State. 'You haven't created any orders yet' and 'No results match your search' are both empty lists, but they call for completely different messaging and actions. Designing one generic 'No data' message for every empty case misses the chance to actually help the user in each specific situation.
Why should a 'no search results' empty state look different from a 'you have no orders yet' empty state, even though both show an empty list?
- →The underlying reason and appropriate next action differ between the two situations
- →It's purely a random visual styling preference with no functional reason
The Anatomy of a Good Empty State. A well-designed empty state has three parts: a clear explanation of WHY it's empty, an optional supporting visual (an icon or illustration), and — most importantly — a clear next action the user can actually take, like a 'Create your first project' button.
Distinguishing Empty from Loading from Error. These are three genuinely distinct states that need to be checked in the right order: loading first, then error, then — only once you have real, confirmed data — check whether that data is empty. Mixing up the order can show an empty state while data is still loading, which is misleading.
Why must the 'is the data empty?' check happen AFTER the loading and error checks, not before them?
- →Checking emptiness too early could show 'no data' while data is still actually loading
- →JavaScript syntax strictly requires this specific check order
Mastery Achieved. You now understand empty states: designing them per-context instead of one generic message, the three-part anatomy of explanation plus action, and correctly ordering your loading/error/empty checks. Next, you'll pull loading, empty, and error handling together into a single, reusable Fallback UI approach.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1An Empty State's Action Button Needs a Clear, Specific Label
Avoid vague labels like 'Click here' on an empty state's call-to-action button — a screen reader user tabbing through buttons out of context benefits from a specific label like 'Create your first project'.
SEO Implications
- 1
A Thoughtful Empty State Improves Perceived Quality for First-Time Crawls Too
For pages that might be crawled before any user-generated content exists, a well-authored empty state provides meaningful, indexable text content instead of an effectively blank page.
Best Practices
Write Empty State Copy Specific to the Actual Context
A search results empty state and a 'never had any data' empty state should have distinctly different messages and calls to action, matching what actually caused the emptiness.
Always Include a Concrete Next Action When One Exists
If there's a meaningful action a user could take to resolve the empty state (create an item, adjust filters, clear a search), surface it directly as a button or link, not just as descriptive text.
Frequent Bugs
A page briefly shows an empty state message before the real data finishes loading.
The emptiness check is running before the loading state has been checked. Reorder the conditional logic so loading is checked first, then error, and only then whether the confirmed data is empty.
Every empty list in the app shows the exact same generic 'No data available' message regardless of context.
Replace the generic message with context-specific empty state components, each explaining the actual reason for emptiness and offering an appropriate next action for that specific situation.
Real-World Examples
Two Different Empty States for the Same List Component
An order history page can be empty either because a new user has never placed an order, or because an applied date filter matches no orders. The component checks which case applies and renders EmptyOrdersState ('Start shopping') for the first and EmptyFilterResults ('Try a different date range') for the second, giving each situation an appropriately specific message and action.
function OrderHistory({ orders, hasActiveFilter }) {
if (orders.length === 0) {
return hasActiveFilter ? <EmptyFilterResults /> : <EmptyOrdersState />;
}
return <OrderList orders={orders} />;
}