@shubhh6003/react-native-empty-state-views
v0.1.0
Published
Production-ready, highly customizable empty state, offline, and error screens for React Native and Expo with zero runtime dependencies.
Downloads
200
Maintainers
Readme
@shubhh6003/react-native-empty-state-views
Ready-made, highly customizable, and accessible empty state, offline, and error status screens for React Native and Expo with zero runtime dependencies.
Preview
| Light Mode | Dark Mode |
| :---: | :---: |
|
|
|
(Screenshots and demo GIFs can be added to the ./assets/ directory upon release).
Features
- 🚀 Zero Runtime Dependencies: No
react-native-svg, vector-icon fonts, or Lottie required. All icons are drawn with pure, high-performanceViewprimitives. - 📦 Ready-to-Use Presets: Built-in
NoData,NoInternet,ErrorState, andNoSearchResultsscreens with sensible defaults. - 🔄 Smart Async Retry Handling: Pass an async
onRetrycallback; the button automatically manages loading spinners (ActivityIndicator), disables concurrent taps, recovers from rejections, and respects cooldown intervals (retryCooldownMs). - 🎛️
StateViewOrchestrator: Seamlessly swap betweenloading,empty,offline,error, andsuccessscreens with a clean declarative API. - 🎨 Deeply Themeable: Automatic system appearance tracking (
light/dark/auto), per-variant tint colors, and global font overrides. - 🌐 Global Provider & i18n Ready: Customize all copy and theme tokens globally using
EmptyStateProvideranduseEmptyStateConfig. - ♿ Accessible by Default: Screen reader announcements on mount (
announceForAccessibility), proper roles (alertfor error/offline,summaryfor data), minimum 48x48 touch targets, andmaxFontSizeMultiplierfont scaling protection. - 📱 FlatList Integration: Purpose-built for
ListEmptyComponentand compact card embeds.
Installation
# npm
npm install @shubhh6003/react-native-empty-state-views
# yarn
yarn add @shubhh6003/react-native-empty-state-views
# expo
npx expo install @shubhh6003/react-native-empty-state-viewsPeer Dependencies:
react >= 18.0.0andreact-native >= 0.70.0. Works out of the box in Expo managed workflow and bare React Native.
Quick Start
import React from 'react';
import { View } from 'react-native';
import { NoData, NoInternet, ErrorState } from '@shubhh6003/react-native-empty-state-views';
export default function ExampleScreen() {
const handleRefresh = async () => {
await fetchLatestData();
};
return (
<View style={{ flex: 1 }}>
{/* Ready-made No Data screen */}
<NoData onRetry={handleRefresh} />
{/* Or offline state with custom retry action */}
{/* <NoInternet onRetry={handleRefresh} retryLabel="Check connection" /> */}
{/* Or error state */}
{/* <ErrorState onRetry={handleRefresh} /> */}
</View>
);
}Presets Showcase
All presets are lightweight wrappers around the base EmptyState component:
1. NoData
Designed for empty lists, blank feeds, and empty inbox screens.
import { NoData } from '@shubhh6003/react-native-empty-state-views';
<NoData
title="Nothing here yet"
description="Your saved articles will appear here once bookmarked."
onRetry={fetchBookmarks}
retryLabel="Refresh"
/>2. NoInternet
Designed for offline detection and network drops. Announces state to screen readers on mount with accessibilityRole="alert".
import { NoInternet } from '@shubhh6003/react-native-empty-state-views';
<NoInternet
onRetry={checkConnection}
secondaryActionLabel="Open Network Settings"
onSecondaryAction={openSettings}
/>3. ErrorState
Designed for 500 server errors, rejected API requests, or runtime exceptions.
import { ErrorState } from '@shubhh6003/react-native-empty-state-views';
<ErrorState
title="Something went wrong"
description="Could not process payment. Your card was not charged."
onRetry={processPaymentAgain}
retryLabel="Try Again"
/>4. NoSearchResults
Designed for search queries returning 0 results.
import { NoSearchResults } from '@shubhh6003/react-native-empty-state-views';
<NoSearchResults
title="No results found"
description="Try checking for spelling errors or searching for a different keyword."
onRetry={clearSearch}
retryLabel="Clear Search"
/>Full Props Reference (EmptyState and Presets)
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| variant | 'noData' \| 'noInternet' \| 'error' \| 'noResults' \| 'custom' | 'custom' | Variant presets providing default icon, title, description, and tints. |
| icon | ReactNode \| ImageSourcePropType \| null | preset icon | Custom icon component, image source, or null to hide the icon. |
| iconSize | number | 96 (64 in compact) | Icon dimension in points. |
| iconColor | string | theme color | Tint color applied to built-in icons and circular badge background. |
| title | string | preset title | Primary heading text. |
| description | string | preset desc | Secondary explanatory text. |
| renderTitle | () => ReactNode | undefined | Custom renderer to completely replace heading Text. |
| renderDescription | () => ReactNode | undefined | Custom renderer to completely replace description Text. |
| onRetry | () => void \| Promise<void> | undefined | If provided, displays primary action button. Automatically manages async spinner if returns a Promise. |
| retryLabel | string | 'Try again' | Button text label. |
| retrying | boolean | false | Controlled loading state for the retry button. |
| retryCooldownMs | number | 0 | Cooldown period in ms to disable button after being tapped. |
| secondaryActionLabel | string | undefined | Label for optional secondary outline button. |
| onSecondaryAction | () => void | undefined | Callback for secondary button. |
| renderActions | () => ReactNode | undefined | Custom renderer replacing all action buttons. |
| fullScreen | boolean | true | Centers within parent with flex: 1. Set false for card wraps. |
| compact | boolean | false | Renders 30% scaled-down version for card embeds or list footers. |
| theme | 'light' \| 'dark' \| 'auto' | 'auto' | Theme mode. 'auto' listens to device useColorScheme(). |
| animated | boolean | true | Enables slide-up + fade-in entrance animation (respects OS reduced motion). |
| containerStyle | StyleProp<ViewStyle> | undefined | Outer container style override. |
| iconContainerStyle | StyleProp<ViewStyle> | undefined | Style applied to icon container wrapper. |
| titleStyle | StyleProp<TextStyle> | undefined | Heading text style override. |
| descriptionStyle | StyleProp<TextStyle> | undefined | Description text style override. |
| buttonStyle | StyleProp<ViewStyle> | undefined | Primary action button style override. |
| buttonTextStyle | StyleProp<TextStyle> | undefined | Primary button text style override. |
| secondaryButtonStyle | StyleProp<ViewStyle> | undefined | Secondary button container style override. |
| secondaryButtonTextStyle | StyleProp<TextStyle> | undefined | Secondary button text style override. |
| testID | string | undefined | Base testID. Sub-elements receive ${testID}-icon, -title, -description, -retry, -secondary-action. |
| accessibilityLabel | string | title + description | Screen reader label for main container. |
Global Provider & Theming
Wrap your app or screen with EmptyStateProvider to define global defaults, translations, and custom branding:
import React from 'react';
import { EmptyStateProvider } from '@shubhh6003/react-native-empty-state-views';
export default function App() {
return (
<EmptyStateProvider
config={{
theme: 'auto',
retryCooldownMs: 1500,
lightTheme: {
buttonBackground: '#7C3AED',
titleColor: '#1E1B4B',
descriptionColor: '#4B5563',
},
darkTheme: {
buttonBackground: '#8B5CF6',
titleColor: '#F9FAFB',
descriptionColor: '#9CA3AF',
},
}}
>
<MainNavigator />
</EmptyStateProvider>
);
}Internationalization (i18n)
All default strings are centralized in a single object. You can override them via EmptyStateProvider or combine them with your favorite i18n library (react-i18next, i18n-js, etc.):
import { EmptyStateProvider } from '@shubhh6003/react-native-empty-state-views';
import i18n from './i18n';
<EmptyStateProvider
config={{
strings: {
noDataTitle: i18n.t('empty.noDataTitle'),
noDataDescription: i18n.t('empty.noDataDescription'),
noInternetTitle: i18n.t('empty.noInternetTitle'),
noInternetDescription: i18n.t('empty.noInternetDescription'),
errorTitle: i18n.t('empty.errorTitle'),
errorDescription: i18n.t('empty.errorDescription'),
noResultsTitle: i18n.t('empty.noResultsTitle'),
noResultsDescription: i18n.t('empty.noResultsDescription'),
retryLabel: i18n.t('empty.retryLabel'),
},
}}
>
<App />
</EmptyStateProvider>FlatList & StateView Usage
1. FlatList with ListEmptyComponent
Important: Always add
contentContainerStyle={{ flexGrow: 1 }}so the empty state centers vertically within the scrollable area!
import React, { useState } from 'react';
import { FlatList, RefreshControl } from 'react-native';
import { NoData } from '@shubhh6003/react-native-empty-state-views';
export function ItemsList() {
const [items, setItems] = useState([]);
const [refreshing, setRefreshing] = useState(false);
const fetchItems = async () => {
setRefreshing(true);
await loadData();
setRefreshing(false);
};
return (
<FlatList
data={items}
keyExtractor={item => item.id}
renderItem={renderItem}
contentContainerStyle={{ flexGrow: 1 }}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={fetchItems} />
}
ListEmptyComponent={
<NoData
title="No Orders Yet"
description="Your placed orders will show up here."
onRetry={fetchItems}
/>
}
/>
);
}2. StateView Orchestrator
When you have a screen that transitions between multiple async states:
import React from 'react';
import { StateView } from '@shubhh6003/react-native-empty-state-views';
export function ProfileOrdersScreen({ status, orders, reload }) {
// status can be: 'loading' | 'empty' | 'offline' | 'error' | 'success'
return (
<StateView
status={status}
onRetry={reload}
errorProps={{ title: 'Failed to load orders' }}
emptyProps={{ title: 'No orders placed' }}
offlineProps={{ title: 'You are offline' }}
>
<OrdersFeed data={orders} />
</StateView>
);
}NetInfo Integration Recipe
This library intentionally has zero runtime dependencies and does not require @react-native-community/netinfo. Here is how you can effortlessly combine them in your project:
npm install @react-native-community/netinfoimport React, { useEffect, useState } from 'react';
import NetInfo from '@react-native-community/netinfo';
import { NoInternet } from '@shubhh6003/react-native-empty-state-views';
export function useNetworkStatus() {
const [isConnected, setIsConnected] = useState<boolean | null>(true);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected);
});
return () => unsubscribe();
}, []);
return isConnected;
}
// In your screen component:
export function DashboardScreen() {
const isConnected = useNetworkStatus();
if (!isConnected) {
return (
<NoInternet
onRetry={() => NetInfo.fetch()}
retryLabel="Check Connection"
/>
);
}
return <NormalContent />;
}Accessibility Notes
- Screen Reader Announcements: Mounts of
errorandnoInternetvariants announce${title}. ${description}to VoiceOver/TalkBack usingAccessibilityInfo.announceForAccessibility. - Accessibility Roles: Container sets
accessibilityRole="alert"for errors and offline, and"summary"for data states. Buttons useaccessibilityRole="button". - Touch Targets: All buttons respect minimum 48x48 point touch targets.
- Font Scaling Protection: Text elements include
maxFontSizeMultiplier={1.4}to ensure large accessibility font sizes do not break layout hierarchy. - Reduced Motion: Automatically queries
AccessibilityInfo.isReduceMotionEnabled()to disable entrance transitions if the user prefers reduced motion. - WCAG AA Compliance: Default light and dark themes comply with WCAG 2.1 AA color contrast standards.
FAQ & Troubleshooting
Why is ListEmptyComponent not vertically centered?
Make sure you specify contentContainerStyle={{ flexGrow: 1 }} on your <FlatList> or <ScrollView>. By default, FlatList's internal content container wraps its children to minimum height.
Why does dark mode not toggle automatically?
Check your app's Appearance settings. If your top-level React Native app or root ThemeProvider forces a color scheme, pass theme="dark" or theme="light" explicitly, or configure your theme within EmptyStateProvider.
Why is the Retry button stuck in loading state?
If you supply an async onRetry returning a Promise, ensure that the Promise resolves or rejects. If you supply the controlled retrying prop, make sure your component state updates retrying to false when finished.
Contributing
Please see CONTRIBUTING.md and CODE_OF_CONDUCT.md for details on code style, tests, and pull request workflows.
License
MIT © shubhh6003
