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

react-native-beacon-kit

v0.10.0

Published

iBeacon & AltBeacon for React Native — New Architecture (TurboModules + JSI) with real background scanning.

Readme

react-native-beacon-kit

iBeacon, AltBeacon, and Eddystone-UID for React Native with a hooks-first API, New Architecture support, and real Android background scanning.

Platform support: Android and iOS

Why this library

  • Hooks-first API for React apps
  • Low-level API for custom orchestration
  • Android background scanning with foreground service support
  • iOS monitoring flow for region entry and exit
  • iBeacon, AltBeacon, and Eddystone-UID support (Eddystone on Android)
  • Optional Kalman filter for more stable distance readings
  • Environment diagnostics for Bluetooth, location services, and permissions
  • Expo plugin included
  • Compatible with Android 16 KB page size (Android 15+)

When to use each API

| API | Use it when | | ------------------------- | ------------------------------------------------------------------ | | useBeaconRanging() | You want nearby iBeacon/AltBeacon readings, RSSI, and distance | | useBeaconMonitoring() | You only need inside / outside region state | | useMonitorThenRange() | You want monitoring to wake the flow, then range only while inside | | useEddystoneRanging() | You want nearby Eddystone-UID readings on Android | | Beacon.* | You need custom orchestration or a non-React flow |

If you are building a screen in React, start with the hooks API.

Installation

React Native (bare)

npm install react-native-beacon-kit

Then for iOS:

cd ios && pod install

Expo bare workflow

npx expo install react-native-beacon-kit

Add the plugin to your app.json or app.config.js:

{
  "expo": {
    "plugins": ["react-native-beacon-kit"]
  }
}

Optional iOS background location capability:

{
  "expo": {
    "plugins": [
      [
        "react-native-beacon-kit",
        {
          "iosBackgroundLocation": true
        }
      ]
    ]
  }
}

Then run prebuild to apply the native changes and install iOS pods:

npx expo prebuild

The plugin automatically adds the required Android permissions, iOS Info.plist usage strings, and — if iosBackgroundLocation is enabled — the background location mode. It does not request runtime permissions for you.

Quick start (High-level API)

This is the shortest useful flow for most apps:

  1. Mount the hook
  2. Request permissions
  3. Call Beacon.configure()
  4. Call start()
import { useCallback } from 'react';
import { Button, Text, View } from 'react-native';
import Beacon, { useBeaconRanging } from 'react-native-beacon-kit';

const region = {
  identifier: 'store',
  uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
};

export const BeaconScreen = () => {
  const { beacons, error, isActive, start, stop } = useBeaconRanging({
    region,
  });

  const handleStart = useCallback(async () => {
    try {
      // Request runtime permissions first.
      await requestPermissions();

      // Configure the native scanner before starting it.
      Beacon.configure({
        scanPeriod: 1100,
        backgroundScanPeriod: 10000,
        betweenScanPeriod: 0,
      });

      await start();
    } catch (error) {
      console.warn('[beacon] start failed', error);
    }
  }, [start]);

  return (
    <View>
      <Text>Detected beacons: {beacons.length}</Text>
      {error ? <Text>{error.message}</Text> : null}
      <Button
        title={isActive ? 'Stop' : 'Start'}
        onPress={isActive ? stop : handleStart}
      />
    </View>
  );
};

This shows the smallest useful flow. In a real app, you will usually centralize permissions and Beacon.configure() at the app level, then use hooks inside screens.

Important Android note

On Android SDK 34+, call Beacon.configure({ foregroundService: true }) only after permissions are granted. Calling it too early can throw a SecurityException on a fresh install.

Recommended app-level setup

For apps with multiple screens or shared beacon behavior, do permissions and initial configuration once at the app level, then let screens use the hooks.

import { useEffect } from 'react';
import Beacon from 'react-native-beacon-kit';

export const App = () => {
  useEffect(() => {
    const setup = async () => {
      await requestPermissions();

      Beacon.configure({
        scanPeriod: 1100,
        backgroundScanPeriod: 10000,
        betweenScanPeriod: 0,
        foregroundService: true,
        foregroundServiceNotification: {
          title: 'My App',
          text: 'Scanning for beacons...',
        },
      });
    };

    setup().catch((error) => {
      console.warn('[beacon] app setup failed', error);
    });
  }, []);

  return <Screens />;
};

For a fuller working example, see the example/ app in this repository: react-native-beacon-kit example

Low-level API equivalent

Use this style if you need manual orchestration outside the hooks.

import { useCallback, useEffect, useState } from 'react';
import { Button, Text, View } from 'react-native';
import Beacon from 'react-native-beacon-kit';

const region = {
  identifier: 'store',
  uuid: 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
};

export const BeaconScreen = () => {
  const [beacons, setBeacons] = useState([]);
  const [error, setError] = useState<string | null>(null);
  const [isActive, setIsActive] = useState(false);

  useEffect(() => {
    const rangingSub = Beacon.onBeaconsRanged((event) => {
      if (event.region.identifier !== region.identifier) return;
      setError(null);
      setBeacons(event.beacons);
    });

    const failureSub = Beacon.onRangingFailed((event) => {
      if (event.region?.identifier !== region.identifier) return;
      setError(event.message);
    });

    return () => {
      rangingSub.remove();
      failureSub.remove();
    };
  }, []);

  const handleStart = useCallback(async () => {
    try {
      await requestPermissions();
      Beacon.configure({
        scanPeriod: 1100,
        backgroundScanPeriod: 10000,
        betweenScanPeriod: 0,
      });
      await Beacon.startRanging(region);
      setIsActive(true);
    } catch (error) {
      console.warn('[beacon] start failed', error);
    }
  }, []);

  const handleStop = useCallback(async () => {
    await Beacon.stopRanging(region);
    setBeacons([]);
    setIsActive(false);
  }, []);

  return (
    <View>
      <Text>Detected beacons: {beacons.length}</Text>
      {error ? <Text>{error}</Text> : null}
      <Button
        title={isActive ? 'Stop' : 'Start'}
        onPress={isActive ? handleStop : handleStart}
      />
    </View>
  );
};

Platform setup

The library declares native permissions, but runtime permission requests are your responsibility.

Android

Use react-native-permissions or your own runtime flow.

| Permission | When required | | ----------------------------- | ------------------------------------------- | | ACCESS_FINE_LOCATION | Always | | ACCESS_BACKGROUND_LOCATION | Android 10+ background scanning | | BLUETOOTH_SCAN | Android 12+ | | BLUETOOTH_CONNECT | Android 12+ | | FOREGROUND_SERVICE | Android background scanning | | FOREGROUND_SERVICE_LOCATION | Android 14+ location foreground service | | POST_NOTIFICATIONS | Android 13+ foreground service notification |

Important notes:

  • ACCESS_BACKGROUND_LOCATION must be requested separately at runtime
  • on Android 13+, POST_NOTIFICATIONS should be requested at runtime too
  • background scanning requires foregroundService: true

iOS

If you are using the Expo plugin, these keys are added automatically by npx expo prebuild. No manual changes to Info.plist are needed.

If you are configuring iOS manually, add these keys to Info.plist:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app uses your location to detect nearby beacons.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app uses your location to detect nearby beacons.</string>

For background region monitoring, enable Location updates in your target's Background Modes capability. If you are using the Expo plugin, set iosBackgroundLocation: true instead and let prebuild handle it.

Platform behavior and limits

Android

  • Real background scanning requires foregroundService: true
  • OEM power management can still affect reliability with the screen off
  • If you need the strongest background behavior on restrictive devices, see Advanced Android

Behavior by API level:

| API level | What changes | | --------- | ------------ | | Android 10 (API 29) | ACCESS_BACKGROUND_LOCATION must be requested separately at runtime | | Android 12 (API 31) | BLUETOOTH_SCAN and BLUETOOTH_CONNECT are required in addition to location | | Android 13 (API 33) | POST_NOTIFICATIONS required to show the foreground service notification | | Android 14 (API 34) | FOREGROUND_SERVICE_LOCATION required for location-type foreground services; calling configure({ foregroundService: true }) before permissions are granted throws a SecurityException |

iOS

  • Continuous background ranging is not supported
  • Use monitoring to wake the app, then start ranging during the available execution window
  • Android-only options such as foregroundService, aggressiveBackground, scanPeriod, backgroundScanPeriod, and betweenScanPeriod are ignored on iOS
  • Region limit: CoreLocation allows a maximum of 20 simultaneously monitored regions per app. If you register more than 20, iOS silently drops the extras. Prefer fewer, broader regions where possible.
  • Simulator: the iOS Simulator does not have Bluetooth hardware. You need a physical device to scan for beacons.

Typical iOS background pattern:

Beacon.startMonitoring({ identifier: 'my-region', uuid: '...' });

const sub = Beacon.onRegionStateChanged(({ state }) => {
  if (state === 'inside') {
    Beacon.startRanging({ identifier: 'my-region', uuid: '...' });
  }
});

API summary

Hooks

Use these hooks if you are building a React screen.

Shared options for the beacon workflow hooks:

  • region: the beacon region to range or monitor
  • autoStart?: start automatically when the hook mounts
  • stopOnUnmount?: stop automatically when the component unmounts

Defaults:

  • autoStart = false
  • stopOnUnmount = true

useBeaconRanging({ region, autoStart?, stopOnUnmount? })

Best for foreground proximity UI when you want nearby beacon readings, RSSI, and distance.

Returns beacon data plus workflow state:

  • beacons
  • error
  • isActive, isStarting, isStopping
  • clearError()
  • start(), stop()

useBeaconMonitoring({ region, autoStart?, stopOnUnmount? })

Best when you only need region entry and exit state without continuous ranging.

Returns monitoring state plus workflow state:

  • regionState
  • error
  • isActive, isStarting, isStopping
  • clearError()
  • start(), stop()

useMonitorThenRange({ region, autoStart?, stopOnUnmount? })

Best when you want a battery-friendlier workflow that monitors first and ranges only while the device is inside the region.

Returns combined monitoring and ranging state:

  • beacons
  • regionState
  • isRanging
  • error
  • isActive, isStarting, isStopping
  • clearError()
  • start(), stop()

useEddystoneRanging({ region, autoStart?, stopOnUnmount? })

Android only. Ranges Eddystone-UID beacons by namespace and optional instance. The region uses namespace and instance instead of uuid, major, and minor.

import { useEddystoneRanging } from 'react-native-beacon-kit';

const region = {
  identifier: 'my-namespace',
  namespace: 'a1b23c45d67e9fab0034',
  instance: '0034567890ab', // optional — omit to match any instance
};

const { beacons, isActive, start, stop } = useEddystoneRanging({ region });

Each beacon in the beacons array:

interface EddystoneUidReading {
  namespace: string;  // 10-byte hex string
  instance: string;   // 6-byte hex string
  rssi: number;
  distance: number;
  rawDistance: number;
  txPower: number;
  macAddress: string;
  timestamp: number;
}

Platform note: iOS does not support Eddystone natively. useEddystoneRanging works on Android only. On iOS the hook mounts without error but beacons will always be empty.

Environment diagnostics

Use useBeaconEnvironment() if you want a React-friendly view of scanner readiness without manually wiring snapshot reads and event subscriptions.

Returns:

  • state
  • isLoading
  • error
  • refresh()

Example:

import { Text, View } from 'react-native';
import { useBeaconEnvironment } from 'react-native-beacon-kit';

export const BeaconDiagnostics = () => {
  const { state, isLoading, error, refresh } = useBeaconEnvironment();

  if (isLoading) return <Text>Loading diagnostics...</Text>;
  if (error) return <Text>{error.message}</Text>;
  if (!state) return <Text>Diagnostics unavailable</Text>;

  return (
    <View>
      <Text>Foreground ready: {state.canScanInForeground ? 'yes' : 'no'}</Text>
      <Text>Background ready: {state.canScanInBackground ? 'yes' : 'no'}</Text>
      <Text>Bluetooth: {state.bluetoothEnabled ? 'on' : 'off'}</Text>
      <Text>
        Location services: {state.locationServicesEnabled ? 'on' : 'off'}
      </Text>
      <Text onPress={() => void refresh()}>Refresh diagnostics</Text>
    </View>
  );
};

Low-level API

Use Beacon.* if you need custom orchestration outside the hooks.

Beacon.configure(config)

Call this after permissions are granted and before starting a scan.

Beacon.configure({
  scanPeriod?: number,
  backgroundScanPeriod?: number,
  betweenScanPeriod?: number,
  foregroundService?: boolean,
  foregroundServiceNotification?: {
    title?: string,
    text?: string,
  },
  kalmanFilter?: {
    enabled: boolean,
    q?: number,
    r?: number,
  },
  aggressiveBackground?: boolean,
});

Recommended starting point:

Beacon.configure({
  scanPeriod: 1100,
  backgroundScanPeriod: 10000,
  betweenScanPeriod: 0,
});

Key fields:

  • foregroundService: enables real Android background scanning
  • kalmanFilter: smooths distance readings
  • aggressiveBackground: Android-only fallback for restrictive OEM devices

Beacon.checkPermissions(): Promise<boolean>

Returns true if all required permissions are already granted. It does not request them.

Beacon.getEnvironmentState()

Returns a snapshot of the current scanning environment:

const state = await Beacon.getEnvironmentState();
// {
//   bluetoothEnabled,
//   locationServicesEnabled,
//   locationPermissionGranted,
//   bluetoothPermissionGranted,
//   backgroundPermissionGranted,
//   permissionsGranted,
//   canScanInForeground,
//   canScanInBackground,
// }

Beacon.startRanging(region) / Beacon.stopRanging(region)

Starts or stops nearby beacon ranging with RSSI and distance.

Beacon.startMonitoring(region) / Beacon.stopMonitoring(region)

Starts or stops region entry and exit monitoring.

Events

  • Beacon.onBeaconsRanged(callback) — iBeacon / AltBeacon ranging results
  • Beacon.onEddystoneRanged(callback) — Eddystone-UID ranging results (Android only)
  • Beacon.onRegionStateChanged(callback)
  • Beacon.onRangingFailed(callback)
  • Beacon.onMonitoringFailed(callback)
  • Beacon.onScannerStateChanged(callback)

For hook users, runtime failures are already exposed through each hook's error field.

Beacon object

interface Beacon {
  uuid: string;
  major: number;
  minor: number;
  rssi: number;
  distance: number;
  rawDistance: number;
  txPower: number;
  macAddress: string;
  timestamp: number;
}

Advanced Android

This section is only for apps that need strong screen-off reliability on Android.

Foreground service

Android background scanning requires:

Beacon.configure({
  foregroundService: true,
});

Without a foreground service, Android can stop scanning after the app goes to the background.

Battery optimization

Some devices are much more aggressive than stock Android. If testing shows background scanning is being throttled, you may need to ask the user to disable battery optimization:

const exempt = await Beacon.isIgnoringBatteryOptimizations();
if (!exempt) {
  Beacon.requestIgnoreBatteryOptimizations();
}

aggressiveBackground

aggressiveBackground is off by default and is not needed for most apps.

Use it only if you have verified that screen-off scanning is still being suspended on the target hardware:

Beacon.configure({
  foregroundService: true,
  aggressiveBackground: true,
});

Tradeoff:

  • improves survivability on some OEMs
  • increases battery usage

OEM settings

Some OEMs, especially Xiaomi / HyperOS, may require extra manual setup beyond normal Android permissions and foreground service behavior.

You can deep-link the user to the relevant settings page:

Beacon.openAutostartSettings();

Call openAutostartSettings() only from a user-initiated action, not during app startup.

Background wake-up

When the OS kills your app and a beacon event brings it back, the JS bundle starts completely fresh — no React state, no in-memory store. You need to restore any context your app needs before handling the event.

Android

With foregroundService: true, the process usually stays alive and wake-up restarts are rare. If the user force-kills the app or the OS terminates it under memory pressure, the foreground service is also stopped and no further scanning happens until the app is launched again.

iOS

Core Location can relaunch your app in the background when a monitored region is crossed. The execution window is short (~5–10 seconds). Check launchOptions in your app entry point to detect this case:

// AppDelegate or your root component on mount
const isBackgroundLaunch =
  launchOptions?.[LocationLaunchOptionsKey.region] != null;

If you are using useMonitorThenRange, start monitoring as early as possible — before any async work — so the region state is established within the execution window.

State hydration

React component state does not survive a background relaunch. If your app needs to know about previous beacon context (last known region state, last seen beacon), persist it to AsyncStorage or MMKV and read it back on startup before your beacon logic runs.

Troubleshooting

SecurityException on fresh install (Android SDK 34+)

configure({ foregroundService: true }) is being called before runtime permissions finish resolving. Await the permission flow first.

Ranging returns 0 beacons

Check the basics first:

  • the beacon is advertising in a supported format
  • permissions were granted
  • Bluetooth and location services are enabled on the device
  • the region filter matches the beacon UUID, and optionally major / minor

No foreground service notification on Android 13+

Add POST_NOTIFICATIONS to your runtime permission flow.

Scanning stops after screen off on Xiaomi / HyperOS

Review Advanced Android. foregroundService: true may not be enough on some OEM devices.

Validated hardware

The following combinations have been tested against this library. If you validate additional hardware, contributions to this table are welcome.

Beacon vendors

| Vendor | Protocol | Tested | | ------ | -------- | ------ | | Estimote | iBeacon | Pending | | Kontakt.io | iBeacon | Pending | | Minew | iBeacon | Pending |

Android versions

| Android version | API level | Tested | | --------------- | --------- | ------ | | Android 12 | 31 | Pending | | Android 13 | 33 | Pending | | Android 14 | 34 | Pending | | Android 15 | 35 | Pending |

iOS versions

| iOS version | Tested | | ----------- | ------ | | iOS 16 | Pending | | iOS 17 | Pending | | iOS 18 | Pending |

This matrix will be updated as validation is completed. Releases that include real-device validation will note it in the CHANGELOG.

License

MIT