npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

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.

npm version npm downloads license CI TypeScript Platform


Preview

| Light Mode | Dark Mode | | :---: | :---: | | Light Mode Preview | Dark Mode Preview |

(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-performance View primitives.
  • 📦 Ready-to-Use Presets: Built-in NoData, NoInternet, ErrorState, and NoSearchResults screens with sensible defaults.
  • 🔄 Smart Async Retry Handling: Pass an async onRetry callback; the button automatically manages loading spinners (ActivityIndicator), disables concurrent taps, recovers from rejections, and respects cooldown intervals (retryCooldownMs).
  • 🎛️ StateView Orchestrator: Seamlessly swap between loading, empty, offline, error, and success screens 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 EmptyStateProvider and useEmptyStateConfig.
  • ♿ Accessible by Default: Screen reader announcements on mount (announceForAccessibility), proper roles (alert for error/offline, summary for data), minimum 48x48 touch targets, and maxFontSizeMultiplier font scaling protection.
  • 📱 FlatList Integration: Purpose-built for ListEmptyComponent and 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-views

Peer Dependencies: react >= 18.0.0 and react-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/netinfo
import 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 error and noInternet variants announce ${title}. ${description} to VoiceOver/TalkBack using AccessibilityInfo.announceForAccessibility.
  • Accessibility Roles: Container sets accessibilityRole="alert" for errors and offline, and "summary" for data states. Buttons use accessibilityRole="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